From 9dc36e22af4bd9af9550d80408ab43c4e8b5b92e Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 20 Jan 2025 19:11:31 +0100 Subject: [PATCH 01/40] feat: serialize different permissions type to the API format --- .../io/weaviate/client/v1/rbac/Roles.java | 57 +++++++++ .../v1/rbac/api/WeaviatePermission.java | 54 ++++++++ .../client/v1/rbac/api/WeaviateRole.java | 32 +++++ .../v1/rbac/model/BackupsPermission.java | 33 +++++ .../v1/rbac/model/ClusterPermission.java | 31 +++++ .../v1/rbac/model/CollectionsPermission.java | 44 +++++++ .../client/v1/rbac/model/DataPermission.java | 46 +++++++ .../client/v1/rbac/model/NodesPermission.java | 50 ++++++++ .../client/v1/rbac/model/Permission.java | 45 +++++++ .../weaviate/client/v1/rbac/model/Role.java | 15 +++ .../client/v1/rbac/model/RolesPermission.java | 34 +++++ .../v1/rbac/model/TenantsPermission.java | 41 ++++++ .../client/v1/rbac/model/UsersPermission.java | 32 +++++ .../client/v1/rbac/model/PermissionTest.java | 118 ++++++++++++++++++ 14 files changed, 632 insertions(+) create mode 100644 src/main/java/io/weaviate/client/v1/rbac/Roles.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/Permission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/Role.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java create mode 100644 src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java 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..d1b808d53 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/Roles.java @@ -0,0 +1,57 @@ +package io.weaviate.client.v1.rbac; + +public class Roles { + public Roles() { + } + + // public Role create(String name, Permission... permissions) { + // } + // + /// ** Get all existing roles. */ + // public void delete(String role) { + // } + // + /// ** + // * 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 void addPermissions(String role, Permission... permissions) { + // } + // + /// ** + // * 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 void removePermissions(String role, Permission... permissions) { + // } + // + /// ** Get all existing roles. */ + // public void getAll() { + // }; + // + /// ** Get permissions associated with a role. */ + // public List getRolePermissions(String role) { + // }; + // + /// ** Get roles assigned to the current user. */ + // public List getUserRoles() { + // }; + // + /// ** Get roles assigned to a user. */ + // public List getUserRoles(String user) { + // }; + // + /// ** Check if a role exists. */ + // public boolean exists(String role) { + // } + // + // public void assign(String user, String... roles) { + // } + // + // public void revoke(String user, String... roles) { + // } +} 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..cbee5b108 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -0,0 +1,54 @@ +package io.weaviate.client.v1.rbac.api; + +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.Permission; +import io.weaviate.client.v1.rbac.model.RolesPermission; +import io.weaviate.client.v1.rbac.model.TenantsPermission; +import io.weaviate.client.v1.rbac.model.UsersPermission; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class WeaviatePermission { + String action; + BackupsPermission backups; + ClusterPermission cluster; + CollectionsPermission collections; + DataPermission data; + NodesPermission nodes; + RolesPermission roles; + TenantsPermission tenants; + UsersPermission users; + + 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 ClusterPermission) { + this.cluster = (ClusterPermission) 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; + } else if (perm instanceof UsersPermission) { + this.users = (UsersPermission) perm; + } + } +} 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..65c232bd6 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -0,0 +1,32 @@ +package io.weaviate.client.v1.rbac.api; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.weaviate.client.v1.rbac.model.Permission; +import io.weaviate.client.v1.rbac.model.Role; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class WeaviateRole { + String name; + List>> permissions; + + public WeaviateRole(Role role) { + this.name = role.name; + this.permissions = mergePermissions(role.permissions); + } + + private static List>> mergePermissions(List> permissions) { + return null; + } + + public Role toRole() { + return null; + } +} 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..3a3469040 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -0,0 +1,33 @@ +package io.weaviate.client.v1.rbac.model; + +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class BackupsPermission implements Permission { + final transient String action; + final String collection; + + public BackupsPermission(Action action, String collection) { + this.action = action.getValue(); + this.collection = collection; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public BackupsPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..4dc1e03d7 --- /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.Getter; + +public class ClusterPermission implements Permission { + final String action; + + public ClusterPermission(Action action) { + this.action = action.getValue(); + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action); + } + + @Override + public ClusterPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..bbd867a49 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -0,0 +1,44 @@ +package io.weaviate.client.v1.rbac.model; + +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +public class CollectionsPermission implements Permission { + final transient String action; + final String collection; + final String tenant; + + public CollectionsPermission(Action action, String collection) { + this(action, collection, "*"); + } + + private CollectionsPermission(Action action, String collection, String tenant) { + this.action = action.getValue(); + this.collection = collection; + this.tenant = tenant; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public CollectionsPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + CREATE("create_collections"), + READ("read_collections"), + UPDATE("update_collections"), + DELETE("delete_collections"), + 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..d8d6aacba --- /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.Getter; + +@Getter +public class DataPermission implements Permission { + final transient String action; + final String collection; + final String object; + final String tenant; + + public DataPermission(Action action, String collection) { + this(action, collection, "*", "*"); + } + + private DataPermission(Action action, String collection, String object, String tenant) { + this.action = action.getValue(); + this.collection = collection; + this.object = object; + this.tenant = tenant; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public DataPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..49680e54e --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -0,0 +1,50 @@ +package io.weaviate.client.v1.rbac.model; + +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +public class NodesPermission implements Permission { + final transient String action; + final String collection; + final Verbosity verbosity; + + public NodesPermission(Action action, Verbosity verbosity) { + this(action, verbosity, "*"); + } + + public NodesPermission(Action action, Verbosity verbosity, String collection) { + this.action = action.getValue(); + this.collection = collection; + this.verbosity = verbosity; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public NodesPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + READ("read_nodes"); + + @Getter + private final String value; + } + + @AllArgsConstructor + public enum Verbosity { + MINIMAL("minimal"), + VERBOSE("verbose"); + + @Getter + private final String value; + } + +} 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..b465df436 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -0,0 +1,45 @@ +package io.weaviate.client.v1.rbac.model; + +import io.weaviate.client.v1.rbac.api.WeaviatePermission; + +public interface Permission

> { + WeaviatePermission toWeaviate(); + + P fromWeaviate(WeaviatePermission perm); + + static ClusterPermission backups(ClusterPermission.Action action) { + return new ClusterPermission(action); + } + + static BackupsPermission backups(BackupsPermission.Action action, String collection) { + return new BackupsPermission(action, collection); + } + + static CollectionsPermission collections(CollectionsPermission.Action action, String collection) { + return new CollectionsPermission(action, collection); + } + + static DataPermission data(DataPermission.Action action, String collection) { + return new DataPermission(action, collection); + } + + static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity) { + return new NodesPermission(action, verbosity); + } + + static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity, String collection) { + return new NodesPermission(action, verbosity, collection); + } + + static RolesPermission roles(RolesPermission.Action action, String role) { + return new RolesPermission(action, role); + } + + static TenantsPermission tenants(TenantsPermission.Action action) { + return new TenantsPermission(action); + } + + static UsersPermission users(UsersPermission.Action action) { + return new UsersPermission(action); + } +} 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..b64e747df --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java @@ -0,0 +1,15 @@ +package io.weaviate.client.v1.rbac.model; + +import java.util.Arrays; +import java.util.List; + +public class Role { + public final String name; + public final List> permissions; + + public Role(String name, Permission... permissions) { + this.name = name; + this.permissions = Arrays.asList(permissions); + } + +} 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..c3fdfdde4 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.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; + +public class RolesPermission implements Permission { + final transient String action; + final String role; + + public RolesPermission(Action action, String role) { + this.action = action.getValue(); + this.role = role; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public RolesPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..608e9dc81 --- /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.Getter; + +@Getter +public class TenantsPermission implements Permission { + final transient String action; + final String tenant; + + public TenantsPermission(Action action) { + this(action, "*"); + } + + private TenantsPermission(Action action, String tenant) { + this.action = action.getValue(); + this.tenant = tenant; + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + } + + @Override + public TenantsPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..f312a2c24 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -0,0 +1,32 @@ + +package io.weaviate.client.v1.rbac.model; + +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class UsersPermission implements Permission { + final String action; + + public UsersPermission(Action action) { + this.action = action.getValue(); + } + + @Override + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action); + } + + @Override + public UsersPermission fromWeaviate(WeaviatePermission perm) { + return null; + } + + @AllArgsConstructor + public enum Action { + 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..72e542897 --- /dev/null +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -0,0 +1,118 @@ +package io.weaviate.client.v1.rbac.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.function.Function; +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[][] toWeaviateTestCases() { + 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.MANAGE, "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("manage_collections", collections), + }, + { + "cluster permission", + (Supplier>) () -> cluster, + new WeaviatePermission("read_cluster"), + }, + { + "tenants permission", + (Supplier>) () -> tenants, + new WeaviatePermission("read_tenants", tenants), + }, + }; + } + + @DataMethod(source = PermissionTest.class, method = "toWeaviateTestCases") + @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 testDefaultDataPermissions() { + 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 testDefaultCollectionsPermissions() { + CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.MANAGE, "Pizza"); + assertThat(perm).as("collection permission must have tenant=*") + .returns("*", CollectionsPermission::getTenant); + } + + @Test + public void testDefaultNodesPermissions() { + 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 testDefaultTenantsPermissions() { + TenantsPermission perm = new TenantsPermission(TenantsPermission.Action.READ); + assertThat(perm).as("tenants permission must have tenant=*") + .returns("*", TenantsPermission::getTenant); + } +} From e7686d91516e9df1c27814b9624d60d0bfd36a01 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 20 Jan 2025 22:05:56 +0100 Subject: [PATCH 02/40] feat: handle permission deserialization --- .../v1/rbac/api/WeaviatePermission.java | 7 --- .../v1/rbac/model/BackupsPermission.java | 12 ++-- .../v1/rbac/model/ClusterPermission.java | 12 ++-- .../v1/rbac/model/CollectionsPermission.java | 11 ++-- .../client/v1/rbac/model/DataPermission.java | 11 ++-- .../client/v1/rbac/model/NodesPermission.java | 26 ++++---- .../client/v1/rbac/model/Permission.java | 61 +++++++++++++++++-- .../client/v1/rbac/model/RolesPermission.java | 12 ++-- .../v1/rbac/model/TenantsPermission.java | 11 ++-- .../client/v1/rbac/model/UsersPermission.java | 11 ++-- .../client/v1/rbac/model/PermissionTest.java | 23 +++++-- 11 files changed, 126 insertions(+), 71 deletions(-) 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 index cbee5b108..2de02441d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -8,7 +8,6 @@ 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 io.weaviate.client.v1.rbac.model.UsersPermission; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -19,13 +18,11 @@ public class WeaviatePermission { String action; BackupsPermission backups; - ClusterPermission cluster; CollectionsPermission collections; DataPermission data; NodesPermission nodes; RolesPermission roles; TenantsPermission tenants; - UsersPermission users; public WeaviatePermission(String action) { this.action = action; @@ -35,8 +32,6 @@ public

> WeaviatePermission(String action, Permission

this.action = action; if (perm instanceof BackupsPermission) { this.backups = (BackupsPermission) perm; - } else if (perm instanceof ClusterPermission) { - this.cluster = (ClusterPermission) perm; } else if (perm instanceof CollectionsPermission) { this.collections = (CollectionsPermission) perm; } else if (perm instanceof DataPermission) { @@ -47,8 +42,6 @@ public

> WeaviatePermission(String action, Permission

this.roles = (RolesPermission) perm; } else if (perm instanceof TenantsPermission) { this.tenants = (TenantsPermission) perm; - } else if (perm instanceof UsersPermission) { - this.users = (UsersPermission) perm; } } } 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 index 3a3469040..cb86d2a09 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +@Getter public class BackupsPermission implements Permission { final transient String action; final String collection; @@ -13,18 +14,17 @@ public BackupsPermission(Action action, String collection) { this.collection = collection; } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); + BackupsPermission(String action, String collection) { + this(CustomAction.fromString(Action.class, action), collection); } @Override - public BackupsPermission fromWeaviate(WeaviatePermission perm) { - return null; + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); } @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { MANAGE("manage_backups"); @Getter 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 index 4dc1e03d7..ce20efa8d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +@Getter public class ClusterPermission implements Permission { final String action; @@ -11,18 +12,17 @@ public ClusterPermission(Action action) { this.action = action.getValue(); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action); + ClusterPermission(String action) { + this(CustomAction.fromString(Action.class, action)); } @Override - public ClusterPermission fromWeaviate(WeaviatePermission perm) { - return null; + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action); } @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { READ("read_cluster"); @Getter 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 index bbd867a49..ecf6e4a66 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -14,6 +14,10 @@ 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) { this.action = action.getValue(); this.collection = collection; @@ -25,13 +29,8 @@ public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); } - @Override - public CollectionsPermission fromWeaviate(WeaviatePermission perm) { - return null; - } - @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { CREATE("create_collections"), READ("read_collections"), UPDATE("update_collections"), 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 index d8d6aacba..13c939055 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -15,6 +15,10 @@ 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) { this.action = action.getValue(); this.collection = collection; @@ -27,13 +31,8 @@ public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); } - @Override - public DataPermission fromWeaviate(WeaviatePermission perm) { - return null; - } - @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { CREATE("create_data"), READ("read_data"), UPDATE("update_data"), 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 index 49680e54e..87d458353 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -1,5 +1,7 @@ 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.Getter; @@ -14,6 +16,14 @@ 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) { this.action = action.getValue(); this.collection = collection; @@ -25,13 +35,8 @@ public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); } - @Override - public NodesPermission fromWeaviate(WeaviatePermission perm) { - return null; - } - @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { READ("read_nodes"); @Getter @@ -40,11 +45,10 @@ public enum Action { @AllArgsConstructor public enum Verbosity { - MINIMAL("minimal"), - VERBOSE("verbose"); - - @Getter - private final String value; + @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 index b465df436..0f1aa1377 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -5,16 +5,45 @@ public interface Permission

> { WeaviatePermission toWeaviate(); - P fromWeaviate(WeaviatePermission perm); - - static ClusterPermission backups(ClusterPermission.Action action) { - return new ClusterPermission(action); - } + @SuppressWarnings("unchecked") + static

> P fromWeaviate(WeaviatePermission perm) { + String action = perm.getAction(); + if (perm.getBackups() != null) { + return (P) new BackupsPermission(action, perm.getBackups().getCollection()); + } else if (perm.getCollections() != null) { + return (P) new CollectionsPermission(action, perm.getCollections().getCollection()); + } else if (perm.getData() != null) { + return (P) new DataPermission(action, perm.getData().getCollection()); + } else if (perm.getNodes() != null) { + NodesPermission out; + NodesPermission nodes = perm.getNodes(); + if (nodes.getCollection() != null) { + out = new NodesPermission(action, perm.getNodes().getVerbosity(), nodes.getCollection()); + } else { + out = new NodesPermission(action, perm.getNodes().getVerbosity()); + } + return (P) out; + } else if (perm.getRoles() != null) { + return (P) new RolesPermission(action, perm.getRoles().getRole()); + } else if (perm.getTenants() != null) { + return (P) new TenantsPermission(action); + } else if (CustomAction.isValid(ClusterPermission.Action.class, action)) { + System.out.println("cluster:" + action); + return (P) new ClusterPermission(action); + } else if (CustomAction.isValid(UsersPermission.Action.class, action)) { + return (P) new UsersPermission(action); + } + return null; + }; static BackupsPermission backups(BackupsPermission.Action action, String collection) { return new BackupsPermission(action, collection); } + static ClusterPermission cluster(ClusterPermission.Action action) { + return new ClusterPermission(action); + } + static CollectionsPermission collections(CollectionsPermission.Action action, String collection) { return new CollectionsPermission(action, collection); } @@ -43,3 +72,25 @@ static UsersPermission users(UsersPermission.Action action) { return new UsersPermission(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/RolesPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java index c3fdfdde4..58adb4a66 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +@Getter public class RolesPermission implements Permission { final transient String action; final String role; @@ -13,18 +14,17 @@ public RolesPermission(Action action, String role) { this.role = role; } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); + RolesPermission(String action, String role) { + this(CustomAction.fromString(Action.class, action), role); } @Override - public RolesPermission fromWeaviate(WeaviatePermission perm) { - return null; + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); } @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { READ("read_roles"), MANAGE("manage_roles"); 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 index 608e9dc81..bbb9f1fea 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -13,6 +13,10 @@ public TenantsPermission(Action action) { this(action, "*"); } + TenantsPermission(String action) { + this(CustomAction.fromString(Action.class, action)); + } + private TenantsPermission(Action action, String tenant) { this.action = action.getValue(); this.tenant = tenant; @@ -23,13 +27,8 @@ public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); } - @Override - public TenantsPermission fromWeaviate(WeaviatePermission perm) { - return null; - } - @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { CREATE("create_tenants"), READ("read_tenants"), UPDATE("update_tenants"), 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 index f312a2c24..22e706f2f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -12,18 +12,17 @@ public UsersPermission(Action action) { this.action = action.getValue(); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action); + UsersPermission(String action) { + this(CustomAction.fromString(Action.class, action)); } @Override - public UsersPermission fromWeaviate(WeaviatePermission perm) { - return null; + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action); } @AllArgsConstructor - public enum Action { + public enum Action implements CustomAction { MANAGE("manage_users"); @Getter 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 index 72e542897..b838bdc4f 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -13,6 +13,7 @@ import org.testcontainers.shaded.org.hamcrest.MatcherAssert; import org.testcontainers.shaded.org.hamcrest.beans.SamePropertyValuesAs; +import com.google.gson.Gson; import com.jparams.junit4.JParamsTestRunner; import com.jparams.junit4.data.DataMethod; @@ -21,7 +22,7 @@ @RunWith(JParamsTestRunner.class) public class PermissionTest { - public static Object[][] toWeaviateTestCases() { + 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"); @@ -75,7 +76,7 @@ public static Object[][] toWeaviateTestCases() { }; } - @DataMethod(source = PermissionTest.class, method = "toWeaviateTestCases") + @DataMethod(source = PermissionTest.class, method = "serializationTestCases") @Test public void testToWeaviate(String name, Supplier> permFunc, WeaviatePermission expected) throws Exception { @@ -88,7 +89,7 @@ private static Matcher sameAs(T expected) { } @Test - public void testDefaultDataPermissions() { + public void testDefaultDataPermission() { DataPermission perm = new DataPermission(DataPermission.Action.MANAGE, "Pizza"); assertThat(perm).as("data permission must have object=* and tenant=*") .returns("*", DataPermission::getObject) @@ -96,23 +97,33 @@ public void testDefaultDataPermissions() { } @Test - public void testDefaultCollectionsPermissions() { + public void testDefaultCollectionsPermission() { CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.MANAGE, "Pizza"); assertThat(perm).as("collection permission must have tenant=*") .returns("*", CollectionsPermission::getTenant); } @Test - public void testDefaultNodesPermissions() { + 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 testDefaultTenantsPermissions() { + 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)); + } } From 7c33b40a0de7a8b184fc3f8b7f357433d44e7ff9 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 22 Jan 2025 19:59:11 +0100 Subject: [PATCH 03/40] feat: expose all existing endpoints in the sync client --- .../io/weaviate/client/WeaviateClient.java | 5 + .../io/weaviate/client/base/BaseClient.java | 4 + .../client/v1/backup/api/BackupCanceler.java | 4 +- .../io/weaviate/client/v1/rbac/Roles.java | 139 +++++++++++------- .../v1/rbac/api/AssignedUsersGetter.java | 39 +++++ .../client/v1/rbac/api/PermissionAdder.java | 41 ++++++ .../client/v1/rbac/api/PermissionChecker.java | 36 +++++ .../client/v1/rbac/api/PermissionRemover.java | 41 ++++++ .../client/v1/rbac/api/RoleAllGetter.java | 33 +++++ .../client/v1/rbac/api/RoleAssigner.java | 46 ++++++ .../client/v1/rbac/api/RoleCreator.java | 37 +++++ .../client/v1/rbac/api/RoleDeleter.java | 25 ++++ .../client/v1/rbac/api/RoleExists.java | 38 +++++ .../client/v1/rbac/api/RoleGetter.java | 31 ++++ .../client/v1/rbac/api/RoleRevoker.java | 47 ++++++ .../client/v1/rbac/api/UserRolesGetter.java | 49 ++++++ .../v1/rbac/api/WeaviatePermission.java | 7 +- .../client/v1/rbac/api/WeaviateRole.java | 22 +-- .../v1/rbac/model/BackupsPermission.java | 5 +- .../v1/rbac/model/ClusterPermission.java | 6 +- .../v1/rbac/model/CollectionsPermission.java | 11 +- .../client/v1/rbac/model/DataPermission.java | 5 +- .../client/v1/rbac/model/NodesPermission.java | 5 +- .../client/v1/rbac/model/Permission.java | 60 ++++---- .../weaviate/client/v1/rbac/model/Role.java | 19 ++- .../client/v1/rbac/model/RolesPermission.java | 5 +- .../v1/rbac/model/TenantsPermission.java | 5 +- .../client/v1/rbac/model/UsersPermission.java | 11 +- .../client/v1/rbac/model/PermissionTest.java | 7 +- .../client/WeaviateDockerCompose.java | 24 ++- .../integration/client/WeaviateVersion.java | 6 +- .../client/rbac/ClientRbacTest.java | 121 +++++++++++++++ 32 files changed, 791 insertions(+), 143 deletions(-) create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleExists.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java create mode 100644 src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java create mode 100644 src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java diff --git a/src/main/java/io/weaviate/client/WeaviateClient.java b/src/main/java/io/weaviate/client/WeaviateClient.java index b597a3523..adf88b0d2 100644 --- a/src/main/java/io/weaviate/client/WeaviateClient.java +++ b/src/main/java/io/weaviate/client/WeaviateClient.java @@ -20,6 +20,7 @@ import io.weaviate.client.v1.grpc.GRPC; import io.weaviate.client.v1.misc.Misc; import io.weaviate.client.v1.misc.api.MetaGetter; +import io.weaviate.client.v1.rbac.Roles; import io.weaviate.client.v1.schema.Schema; public class WeaviateClient { @@ -101,6 +102,10 @@ public GRPC gRPC() { return new GRPC(httpClient, config, tokenProvider); } + public Roles roles() { + return new Roles(httpClient, config); + } + private DbVersionProvider initDbVersionProvider() { MetaGetter metaGetter = new Misc(httpClient, config, null).metaGetter(); DbVersionProvider.VersionGetter getter = () -> Optional.ofNullable(metaGetter.run()) diff --git a/src/main/java/io/weaviate/client/base/BaseClient.java b/src/main/java/io/weaviate/client/base/BaseClient.java index 041703b0d..2b489947e 100644 --- a/src/main/java/io/weaviate/client/base/BaseClient.java +++ b/src/main/java/io/weaviate/client/base/BaseClient.java @@ -46,6 +46,8 @@ private Response sendRequest(String endpoint, Object payload, String method, HttpResponse response = this.sendHttpRequest(endpoint, payload, method); int statusCode = response.getStatusCode(); String responseBody = response.getBody(); + System.out.println("response: " + responseBody); + System.out.println("Status Code: " + String.valueOf(response.getStatusCode())); if (statusCode < 399) { T body = toResponse(responseBody, classOfT); @@ -62,7 +64,9 @@ private Response sendRequest(String endpoint, Object payload, String method, protected HttpResponse sendHttpRequest(String endpoint, Object payload, String method) throws Exception { String address = config.getBaseURL() + endpoint; + System.out.println(method + " request: " + address); String json = toJsonString(payload); + System.out.println("with body: " + json); if (method.equals("POST")) { return client.sendPostRequest(address, json); } 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 index d1b808d53..a33e6b5a7 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/Roles.java +++ b/src/main/java/io/weaviate/client/v1/rbac/Roles.java @@ -1,57 +1,90 @@ 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 { - public Roles() { - } - - // public Role create(String name, Permission... permissions) { - // } - // - /// ** Get all existing roles. */ - // public void delete(String role) { - // } - // - /// ** - // * 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 void addPermissions(String role, Permission... permissions) { - // } - // - /// ** - // * 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 void removePermissions(String role, Permission... permissions) { - // } - // - /// ** Get all existing roles. */ - // public void getAll() { - // }; - // - /// ** Get permissions associated with a role. */ - // public List getRolePermissions(String role) { - // }; - // - /// ** Get roles assigned to the current user. */ - // public List getUserRoles() { - // }; - // - /// ** Get roles assigned to a user. */ - // public List getUserRoles(String user) { - // }; - // - /// ** Check if a role exists. */ - // public boolean exists(String role) { - // } - // - // public void assign(String user, String... roles) { - // } - // - // public void revoke(String user, String... roles) { - // } + + private final HttpClient httpClient; + private final Config config; + + public RoleCreator creator() { + return new RoleCreator(httpClient, config); + } + + /** Get all existing roles. */ + 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 assiciated 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); + } + + public RoleAssigner assigner() { + return new RoleAssigner(httpClient, config); + } + + 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..28b061a90 --- /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 name; + + public AssignedUsersGetter(HttpClient httpClient, Config config) { + super(httpClient, config); + } + + public AssignedUsersGetter withName(String name) { + this.name = name; + 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.name); + } +} 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..1e7988831 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java @@ -0,0 +1,41 @@ +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 PermissionAdder extends BaseClient implements ClientResult { + private String name; + private List> permissions = new ArrayList<>(); + + public PermissionAdder(HttpClient httpClient, Config config) { + super(httpClient, config); + } + + public PermissionAdder withName(String name) { + this.name = name; + return this; + } + + public PermissionAdder withPermissions(Permission... permissions) { + this.permissions = Arrays.asList(permissions); + return this; + } + + @Override + public Result run() { + List permissions = WeaviatePermission.mergePermissions(this.permissions); + return new Result(sendPostRequest(path(), permissions, Void.class)); + } + + private String path() { + return String.format("/authz/roles/%s/add-permissions", this.name); + } +} 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..944574c24 --- /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 name; + private Permission permission; + + public PermissionChecker(HttpClient httpClient, Config config) { + super(httpClient, config); + } + + public PermissionChecker withName(String name) { + this.name = name; + return this; + } + + public PermissionChecker withPermission(Permission permission) { + this.permission = permission; + return this; + } + + @Override + public Result run() { + return new Result(sendPostRequest(path(), permission, Boolean.class)); + } + + private String path() { + return String.format("/authz/roles/%s/has-permission", this.name); + } +} 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..ead51a033 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java @@ -0,0 +1,41 @@ +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 PermissionRemover extends BaseClient implements ClientResult { + private String name; + private List> permissions = new ArrayList<>(); + + public PermissionRemover(HttpClient httpClient, Config config) { + super(httpClient, config); + } + + public PermissionRemover withName(String name) { + this.name = name; + return this; + } + + public PermissionRemover withPermissions(Permission... permissions) { + this.permissions = Arrays.asList(permissions); + return this; + } + + @Override + public Result run() { + List permissions = WeaviatePermission.mergePermissions(this.permissions); + return new Result(sendPostRequest(path(), permissions, Void.class)); + } + + private String path() { + return String.format("/authz/roles/%s/remove-permissions", this.name); + } +} 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..0df82df00 --- /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); + WeaviateRole role = Optional.ofNullable(resp.getBody()).orElse(null); + return new Result(resp.getStatusCode(), role.toRole(), 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 index 2de02441d..26d6b159e 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -1,7 +1,8 @@ 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.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; @@ -44,4 +45,8 @@ public

> WeaviatePermission(String action, Permission

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 index 65c232bd6..2675ac724 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -1,32 +1,24 @@ package io.weaviate.client.v1.rbac.api; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import io.weaviate.client.v1.rbac.model.Permission; import io.weaviate.client.v1.rbac.model.Role; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; @Getter -@Builder -@AllArgsConstructor public class WeaviateRole { String name; - List>> permissions; + List permissions; - public WeaviateRole(Role role) { - this.name = role.name; - this.permissions = mergePermissions(role.permissions); - } - - private static List>> mergePermissions(List> permissions) { - return null; + public WeaviateRole(String name, List> permissions) { + this.name = name; + this.permissions = WeaviatePermission.mergePermissions(permissions); } public Role toRole() { - return null; + 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 index cb86d2a09..a1ffe9fe5 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -5,12 +5,11 @@ import lombok.Getter; @Getter -public class BackupsPermission implements Permission { - final transient String action; +public class BackupsPermission extends Permission { final String collection; public BackupsPermission(Action action, String collection) { - this.action = action.getValue(); + super(action); this.collection = collection; } 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 index ce20efa8d..30ecac3ae 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -5,11 +5,9 @@ import lombok.Getter; @Getter -public class ClusterPermission implements Permission { - final String action; - +public class ClusterPermission extends Permission { public ClusterPermission(Action action) { - this.action = action.getValue(); + super(action); } ClusterPermission(String action) { 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 index ecf6e4a66..6ef54688f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -5,8 +5,7 @@ import lombok.Getter; @Getter -public class CollectionsPermission implements Permission { - final transient String action; +public class CollectionsPermission extends Permission { final String collection; final String tenant; @@ -19,7 +18,7 @@ public CollectionsPermission(Action action, String collection) { } private CollectionsPermission(Action action, String collection, String tenant) { - this.action = action.getValue(); + super(action); this.collection = collection; this.tenant = tenant; } @@ -34,8 +33,10 @@ public enum Action implements CustomAction { CREATE("create_collections"), READ("read_collections"), UPDATE("update_collections"), - DELETE("delete_collections"), - MANAGE("manage_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 index 13c939055..2d88a579f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -5,8 +5,7 @@ import lombok.Getter; @Getter -public class DataPermission implements Permission { - final transient String action; +public class DataPermission extends Permission { final String collection; final String object; final String tenant; @@ -20,7 +19,7 @@ public DataPermission(Action action, String collection) { } private DataPermission(Action action, String collection, String object, String tenant) { - this.action = action.getValue(); + super(action); this.collection = collection; this.object = object; this.tenant = tenant; 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 index 87d458353..44244c751 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -7,8 +7,7 @@ import lombok.Getter; @Getter -public class NodesPermission implements Permission { - final transient String action; +public class NodesPermission extends Permission { final String collection; final Verbosity verbosity; @@ -25,7 +24,7 @@ public NodesPermission(Action action, Verbosity verbosity) { } public NodesPermission(Action action, Verbosity verbosity, String collection) { - this.action = action.getValue(); + super(action); this.collection = collection; this.verbosity = verbosity; } 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 index 0f1aa1377..5b27ff236 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -1,76 +1,80 @@ package io.weaviate.client.v1.rbac.model; import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import lombok.Getter; -public interface Permission

> { - WeaviatePermission toWeaviate(); +public abstract class Permission

> { + @Getter + final transient String action; - @SuppressWarnings("unchecked") - static

> P fromWeaviate(WeaviatePermission perm) { + 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 (P) new BackupsPermission(action, perm.getBackups().getCollection()); + return new BackupsPermission(action, perm.getBackups().getCollection()); } else if (perm.getCollections() != null) { - return (P) new CollectionsPermission(action, perm.getCollections().getCollection()); + return new CollectionsPermission(action, perm.getCollections().getCollection()); } else if (perm.getData() != null) { - return (P) new DataPermission(action, perm.getData().getCollection()); + return new DataPermission(action, perm.getData().getCollection()); } else if (perm.getNodes() != null) { - NodesPermission out; NodesPermission nodes = perm.getNodes(); if (nodes.getCollection() != null) { - out = new NodesPermission(action, perm.getNodes().getVerbosity(), nodes.getCollection()); - } else { - out = new NodesPermission(action, perm.getNodes().getVerbosity()); + return new NodesPermission(action, perm.getNodes().getVerbosity(), nodes.getCollection()); } - return (P) out; + return new NodesPermission(action, perm.getNodes().getVerbosity()); } else if (perm.getRoles() != null) { - return (P) new RolesPermission(action, perm.getRoles().getRole()); + return new RolesPermission(action, perm.getRoles().getRole()); } else if (perm.getTenants() != null) { - return (P) new TenantsPermission(action); + return new TenantsPermission(action); } else if (CustomAction.isValid(ClusterPermission.Action.class, action)) { - System.out.println("cluster:" + action); - return (P) new ClusterPermission(action); + return new ClusterPermission(action); } else if (CustomAction.isValid(UsersPermission.Action.class, action)) { - return (P) new UsersPermission(action); + return new UsersPermission(action); } return null; }; - static BackupsPermission backups(BackupsPermission.Action action, String collection) { + public static BackupsPermission backups(BackupsPermission.Action action, String collection) { return new BackupsPermission(action, collection); } - static ClusterPermission cluster(ClusterPermission.Action action) { + public static ClusterPermission cluster(ClusterPermission.Action action) { return new ClusterPermission(action); } - static CollectionsPermission collections(CollectionsPermission.Action action, String collection) { + public static CollectionsPermission collections(CollectionsPermission.Action action, String collection) { return new CollectionsPermission(action, collection); } - static DataPermission data(DataPermission.Action action, String collection) { + public static DataPermission data(DataPermission.Action action, String collection) { return new DataPermission(action, collection); } - static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity) { + public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity) { return new NodesPermission(action, verbosity); } - static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity, String collection) { + public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity, + String collection) { return new NodesPermission(action, verbosity, collection); } - static RolesPermission roles(RolesPermission.Action action, String role) { + public static RolesPermission roles(RolesPermission.Action action, String role) { return new RolesPermission(action, role); } - static TenantsPermission tenants(TenantsPermission.Action action) { + public static TenantsPermission tenants(TenantsPermission.Action action) { return new TenantsPermission(action); } - static UsersPermission users(UsersPermission.Action action) { - return new UsersPermission(action); - } + // public static UsersPermission users(UsersPermission.Action action) { + // return new UsersPermission(action); + // } } interface CustomAction { 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 index b64e747df..24941453f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Role.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java @@ -1,15 +1,22 @@ package io.weaviate.client.v1.rbac.model; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor public class Role { public final String name; - public final List> permissions; + public List> permissions = new ArrayList<>(); - public Role(String name, Permission... permissions) { - this.name = name; - this.permissions = Arrays.asList(permissions); + 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 index 58adb4a66..a6f3a7c22 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -5,12 +5,11 @@ import lombok.Getter; @Getter -public class RolesPermission implements Permission { - final transient String action; +public class RolesPermission extends Permission { final String role; public RolesPermission(Action action, String role) { - this.action = action.getValue(); + super(action); this.role = role; } 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 index bbb9f1fea..de02a1dcb 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -5,8 +5,7 @@ import lombok.Getter; @Getter -public class TenantsPermission implements Permission { - final transient String action; +public class TenantsPermission extends Permission { final String tenant; public TenantsPermission(Action action) { @@ -18,7 +17,7 @@ public TenantsPermission(Action action) { } private TenantsPermission(Action action, String tenant) { - this.action = action.getValue(); + super(action); this.tenant = tenant; } 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 index 22e706f2f..b7758bf80 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -5,11 +5,14 @@ import lombok.AllArgsConstructor; import lombok.Getter; -public class UsersPermission implements Permission { - final String action; - +/** + * 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) { - this.action = action.getValue(); + super(action); } UsersPermission(String action) { 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 index b838bdc4f..c79720b80 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -1,10 +1,8 @@ package io.weaviate.client.v1.rbac.model; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; -import java.util.function.Function; import java.util.function.Supplier; import org.junit.Test; @@ -13,7 +11,6 @@ import org.testcontainers.shaded.org.hamcrest.MatcherAssert; import org.testcontainers.shaded.org.hamcrest.beans.SamePropertyValuesAs; -import com.google.gson.Gson; import com.jparams.junit4.JParamsTestRunner; import com.jparams.junit4.data.DataMethod; @@ -28,7 +25,7 @@ public static Object[][] serializationTestCases() { 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.MANAGE, "Pizza"); + CollectionsPermission collections = new CollectionsPermission(CollectionsPermission.Action.CREATE, "Pizza"); ClusterPermission cluster = new ClusterPermission(ClusterPermission.Action.READ); TenantsPermission tenants = new TenantsPermission(TenantsPermission.Action.READ); @@ -98,7 +95,7 @@ public void testDefaultDataPermission() { @Test public void testDefaultCollectionsPermission() { - CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.MANAGE, "Pizza"); + CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.CREATE, "Pizza"); assertThat(perm).as("collection permission must have tenant=*") .returns("*", CollectionsPermission::getTenant); } diff --git a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java index bc71c2625..03a890939 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java @@ -15,20 +15,30 @@ public class WeaviateDockerCompose implements TestRule { private final String weaviateVersion; private final boolean withOffloadS3; + private final boolean localServer; public WeaviateDockerCompose() { this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE; this.withOffloadS3 = false; + this.localServer = false; + } + + public WeaviateDockerCompose(boolean localServer) { + this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE; + this.withOffloadS3 = false; + this.localServer = localServer; } public WeaviateDockerCompose(String version) { this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version); this.withOffloadS3 = false; + this.localServer = false; } public WeaviateDockerCompose(String version, boolean withOffloadS3) { this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version); this.withOffloadS3 = withOffloadS3; + this.localServer = false; } public static class Weaviate extends WeaviateContainer { @@ -99,19 +109,29 @@ public void start() { contextionary = new Contextionary(); contextionary.start(); weaviate = new Weaviate(this.weaviateVersion, withOffloadS3); - weaviate.start(); + if (!localServer) { + weaviate.start(); + } } public String getHttpHostAddress() { + if (localServer) { + return "127.0.0.1:8080"; + } return weaviate.getHttpHostAddress(); } public String getGrpcHostAddress() { + if (localServer) { + return "127.0.0.1:50051"; + } return weaviate.getGrpcHostAddress(); } public void stop() { - weaviate.stop(); + if (!localServer) { + weaviate.stop(); + } contextionary.stop(); if (withOffloadS3) { minio.stop(); diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index dc37e52f5..af44bca34 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-f73fcee"; // 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 = "34bb9ae"; private WeaviateVersion() { } 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..be2b095c7 --- /dev/null +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -0,0 +1,121 @@ +package io.weaviate.integration.client.rbac; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.list; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +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; + +public class ClientRbacTest { + private static final String adminRole = "admin"; + private static final String viewerRole = "viewer"; + + private Roles roles; + + @Rule + public TestName currentTest = new TestName(); + + @ClassRule + public static WeaviateDockerCompose compose = new WeaviateDockerCompose(true); + + @Before + public void before() throws AuthException { + Config config = new Config("http", compose.getHttpHostAddress()); + roles = WeaviateAuthClient.apiKey(config, "jp-secret-key").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("result had errors").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); + } + + // TODO: check if I can create a role with a name that's not a valid URL + // paramter + + @Test + public void testCreateAndList() { + String myRole = roleName("VectorOwner"); + String myCollection = "Pizza"; + + try { + // Arrange + roles.deleter().withName(myRole).run(); + + roles.creator().withName(myRole) + .withPermissions( + 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)) + .run(); + + Result response = roles.getter().withName(myRole).run(); + Role role = response.getResult(); + assertThat(response.getError()).as("result had errors").isNull(); + assertThat(role).as("wrong role name").returns(myRole, Role::getName); + + List> permissions = role.getPermissions(); + assertTrue(hasPermissionWithAction(permissions, BackupsPermission.Action.MANAGE.getValue())); + assertTrue(hasPermissionWithAction(permissions, ClusterPermission.Action.READ.getValue())); + assertTrue(hasPermissionWithAction(permissions, NodesPermission.Action.READ.getValue())); + assertTrue(hasPermissionWithAction(permissions, RolesPermission.Action.MANAGE.getValue())); + assertTrue(hasPermissionWithAction(permissions, CollectionsPermission.Action.CREATE.getValue())); + assertTrue(hasPermissionWithAction(permissions, DataPermission.Action.UPDATE.getValue())); + assertTrue(hasPermissionWithAction(permissions, TenantsPermission.Action.DELETE.getValue())); + } finally { + roles.deleter().withName(myRole).run(); + } + } + + /** 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 hasPermissionWithAction(List> permissions, String action) { + return permissions.stream() + .filter(perm -> perm.getAction().equals(action)) + .findFirst().isPresent(); + } +} From 6b0db1ee7ca459c2a7bb98a12f81f96762b38223 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 14:16:58 +0100 Subject: [PATCH 04/40] test: add configuration to run Weaviate Testcontainer with RBAC --- .../client/WeaviateDockerCompose.java | 68 +++++++++++++------ .../client/rbac/ClientRbacTest.java | 7 +- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java index 03a890939..29f5af89f 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java @@ -13,32 +13,40 @@ public class WeaviateDockerCompose implements TestRule { + /** Weaviate Docker image to create a container from. */ private final String weaviateVersion; private final boolean withOffloadS3; - private final boolean localServer; - public WeaviateDockerCompose() { - this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE; - this.withOffloadS3 = false; - this.localServer = false; - } + /** Username of the admin user for instances using RBAC. */ + private final String adminUser; - public WeaviateDockerCompose(boolean localServer) { + public WeaviateDockerCompose() { this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE; this.withOffloadS3 = false; - this.localServer = localServer; + this.adminUser = null; } public WeaviateDockerCompose(String version) { this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version); this.withOffloadS3 = false; - this.localServer = false; + this.adminUser = null; } public WeaviateDockerCompose(String version, boolean withOffloadS3) { this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version); this.withOffloadS3 = withOffloadS3; - this.localServer = false; + 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 { @@ -67,6 +75,28 @@ 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("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false"); + 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 { @@ -108,30 +138,24 @@ public void start() { } contextionary = new Contextionary(); contextionary.start(); - weaviate = new Weaviate(this.weaviateVersion, withOffloadS3); - if (!localServer) { - weaviate.start(); + if (adminUser == null) { + weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3); + } else { + weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3, this.adminUser); } + weaviate.start(); } public String getHttpHostAddress() { - if (localServer) { - return "127.0.0.1:8080"; - } return weaviate.getHttpHostAddress(); } public String getGrpcHostAddress() { - if (localServer) { - return "127.0.0.1:50051"; - } return weaviate.getGrpcHostAddress(); } public void stop() { - if (!localServer) { - weaviate.stop(); - } + weaviate.stop(); contextionary.stop(); if (withOffloadS3) { minio.stop(); diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index be2b095c7..a0261f5bc 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -28,10 +28,12 @@ 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; @@ -39,12 +41,12 @@ public class ClientRbacTest { public TestName currentTest = new TestName(); @ClassRule - public static WeaviateDockerCompose compose = new WeaviateDockerCompose(true); + public static WeaviateDockerCompose compose = WeaviateDockerCompose.rbac(adminUser); @Before public void before() throws AuthException { Config config = new Config("http", compose.getHttpHostAddress()); - roles = WeaviateAuthClient.apiKey(config, "jp-secret-key").roles(); + roles = WeaviateAuthClient.apiKey(config, Weaviate.makeSecret(adminUser)).roles(); } public static Object[][] rolesToCreate() { @@ -78,7 +80,6 @@ public void testCreateAndList() { try { // Arrange roles.deleter().withName(myRole).run(); - roles.creator().withName(myRole) .withPermissions( Permission.backups(BackupsPermission.Action.MANAGE, myCollection), From 312b208f1a5dbd93b741a8852889d72faebeea80 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 16:16:44 +0100 Subject: [PATCH 05/40] test: add integration tests for add-/remove-/has-permission --- .../client/v1/rbac/api/PermissionAdder.java | 16 ++- .../client/v1/rbac/api/PermissionChecker.java | 10 +- .../client/v1/rbac/api/PermissionRemover.java | 16 ++- .../client/v1/rbac/api/RoleGetter.java | 4 +- .../client/v1/rbac/model/Permission.java | 7 +- .../client/rbac/ClientRbacTest.java | 104 +++++++++++++++--- 6 files changed, 121 insertions(+), 36 deletions(-) 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 index 1e7988831..e98c5b65f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java @@ -10,17 +10,18 @@ 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 name; + private String role; private List> permissions = new ArrayList<>(); public PermissionAdder(HttpClient httpClient, Config config) { super(httpClient, config); } - public PermissionAdder withName(String name) { - this.name = name; + public PermissionAdder withRole(String name) { + this.role = name; return this; } @@ -29,13 +30,18 @@ public PermissionAdder withPermissions(Permission... 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(), permissions, Void.class)); + return new Result(sendPostRequest(path(), new Body(permissions), Void.class)); } private String path() { - return String.format("/authz/roles/%s/add-permissions", this.name); + 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 index 944574c24..330e1e96b 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java @@ -8,15 +8,15 @@ import io.weaviate.client.v1.rbac.model.Permission; public class PermissionChecker extends BaseClient implements ClientResult { - private String name; + private String role; private Permission permission; public PermissionChecker(HttpClient httpClient, Config config) { super(httpClient, config); } - public PermissionChecker withName(String name) { - this.name = name; + public PermissionChecker withRole(String role) { + this.role = role; return this; } @@ -27,10 +27,10 @@ public PermissionChecker withPermission(Permission permission) { @Override public Result run() { - return new Result(sendPostRequest(path(), permission, Boolean.class)); + return new Result(sendPostRequest(path(), permission.toWeaviate(), Boolean.class)); } private String path() { - return String.format("/authz/roles/%s/has-permission", this.name); + 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 index ead51a033..2d76eeab3 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java @@ -10,17 +10,18 @@ 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 name; + private String role; private List> permissions = new ArrayList<>(); public PermissionRemover(HttpClient httpClient, Config config) { super(httpClient, config); } - public PermissionRemover withName(String name) { - this.name = name; + public PermissionRemover withRole(String role) { + this.role = role; return this; } @@ -29,13 +30,18 @@ public PermissionRemover withPermissions(Permission... 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(), permissions, Void.class)); + return new Result(sendPostRequest(path(), new Body(permissions), Void.class)); } private String path() { - return String.format("/authz/roles/%s/remove-permissions", this.name); + return String.format("/authz/roles/%s/remove-permissions", this.role); } } 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 index 0df82df00..c07672311 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java @@ -25,7 +25,7 @@ public RoleGetter withName(String name) { @Override public Result run() { Response resp = sendGetRequest("/authz/roles/" + this.name, WeaviateRole.class); - WeaviateRole role = Optional.ofNullable(resp.getBody()).orElse(null); - return new Result(resp.getStatusCode(), role.toRole(), resp.getErrors()); + 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/model/Permission.java b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java index 5b27ff236..74bc61b00 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -37,7 +37,7 @@ public static Permission fromWeaviate(WeaviatePermission perm) { return new UsersPermission(action); } return null; - }; + } public static BackupsPermission backups(BackupsPermission.Action action, String collection) { return new BackupsPermission(action, collection); @@ -75,6 +75,11 @@ public static TenantsPermission tenants(TenantsPermission.Action action) { // public static UsersPermission users(UsersPermission.Action action) { // return new UsersPermission(action); // } + + public String toString() { + return String.format("Permission", this.action); + } + } interface CustomAction { diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index a0261f5bc..9cbc12ade 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -2,7 +2,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.list; +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.assumeTrue; import java.util.List; @@ -14,6 +18,7 @@ import io.weaviate.client.Config; import io.weaviate.client.WeaviateAuthClient; +import io.weaviate.client.WeaviateClient; import io.weaviate.client.base.Result; import io.weaviate.client.v1.auth.exception.AuthException; import io.weaviate.client.v1.rbac.Roles; @@ -73,39 +78,94 @@ public void testGetAll() { // paramter @Test - public void testCreateAndList() { + 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 roles.deleter().withName(myRole).run(); + + // Act roles.creator().withName(myRole) - .withPermissions( - 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)) + .withPermissions(wantPermissions) .run(); + assumeTrue(checkRoleExists(myRole), "role should exist after creation"); Result response = roles.getter().withName(myRole).run(); Role role = response.getResult(); - assertThat(response.getError()).as("result had errors").isNull(); + assertNull("error fetching a role", response.getError()); assertThat(role).as("wrong role name").returns(myRole, Role::getName); - List> permissions = role.getPermissions(); - assertTrue(hasPermissionWithAction(permissions, BackupsPermission.Action.MANAGE.getValue())); - assertTrue(hasPermissionWithAction(permissions, ClusterPermission.Action.READ.getValue())); - assertTrue(hasPermissionWithAction(permissions, NodesPermission.Action.READ.getValue())); - assertTrue(hasPermissionWithAction(permissions, RolesPermission.Action.MANAGE.getValue())); - assertTrue(hasPermissionWithAction(permissions, CollectionsPermission.Action.CREATE.getValue())); - assertTrue(hasPermissionWithAction(permissions, DataPermission.Action.UPDATE.getValue())); - assertTrue(hasPermissionWithAction(permissions, TenantsPermission.Action.DELETE.getValue())); + for (int i = 0; i < wantPermissions.length; i++) { + Permission perm = wantPermissions[i]; + assertTrue("should have permission " + perm, hasPermission(myRole, perm)); + } } finally { roles.deleter().withName(myRole).run(); + assertFalse("should not exist after deletion", checkRoleExists(myRole)); + } + } + + @Test + public void testAddPermissions() { + String myRole = roleName("VectorOwner"); + Permission toAdd = Permission.cluster(ClusterPermission.Action.READ); + try { + // Arrange + roles.creator().withName(myRole) + .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) + .run(); + assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + + // Act + Result addResult = roles.permissionAdder().withRole(myRole) + .withPermissions(toAdd) + .run(); + assertNull("add-permissions operation error", addResult.getError()); + + // Assert + assertTrue("should have permission " + toAdd, hasPermission(myRole, toAdd)); + } finally { + roles.deleter().withName(myRole).run(); + assertFalse("should not exist after deletion", checkRoleExists(myRole)); + } + } + + @Test + public void testRemovePermissions() { + String myRole = roleName("VectorOwner"); + Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE); + try { + // Arrange + roles.creator().withName(myRole) + .withPermissions( + Permission.cluster(ClusterPermission.Action.READ), + Permission.tenants(TenantsPermission.Action.DELETE)) + .run(); + assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + + // Act + Result addResult = roles.permissionRemover().withRole(myRole) + .withPermissions(toRemove) + .run(); + assertNull("remove-permissions operation error", addResult.getError()); + + // Assert + assertFalse("should not have permission " + toRemove, hasPermission(myRole, toRemove)); + } finally { + roles.deleter().withName(myRole).run(); + assertFalse("should not exist after deletion", checkRoleExists(myRole)); } } @@ -114,9 +174,17 @@ private String roleName(String name) { return String.format("%s-%s", currentTest.getMethodName(), name); } + private boolean hasPermission(String role, Permission> perm) { + return roles.permissionChecker().withRole(role).withPermission(perm).run().getResult(); + } + private boolean hasPermissionWithAction(List> permissions, String action) { return permissions.stream() .filter(perm -> perm.getAction().equals(action)) .findFirst().isPresent(); } + + private boolean checkRoleExists(String role) { + return roles.exists().withName(role).run().getResult(); + } } From 96c43011c626be3193a6242452142f5f6e92a168 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 17:21:59 +0100 Subject: [PATCH 06/40] test: add test for getting user roles @EqualsAndHashCode makes comparing Role and Permission objects easy --- .../v1/rbac/model/BackupsPermission.java | 2 ++ .../v1/rbac/model/ClusterPermission.java | 2 ++ .../v1/rbac/model/CollectionsPermission.java | 2 ++ .../client/v1/rbac/model/DataPermission.java | 2 ++ .../client/v1/rbac/model/NodesPermission.java | 2 ++ .../client/v1/rbac/model/Permission.java | 3 +- .../weaviate/client/v1/rbac/model/Role.java | 2 ++ .../client/v1/rbac/model/RolesPermission.java | 2 ++ .../v1/rbac/model/TenantsPermission.java | 2 ++ .../client/rbac/ClientRbacTest.java | 28 ++++++++++++------- 10 files changed, 36 insertions(+), 11 deletions(-) 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 index a1ffe9fe5..e40bafb5e 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -2,9 +2,11 @@ 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; 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 index 30ecac3ae..c70dd7a5f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -2,9 +2,11 @@ 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); 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 index 6ef54688f..a5b97e8b1 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -2,9 +2,11 @@ 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; 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 index 2d88a579f..700119b39 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -2,9 +2,11 @@ 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; 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 index 44244c751..2d2794f7d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -4,9 +4,11 @@ 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; 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 index 74bc61b00..76b1616f3 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -1,8 +1,10 @@ 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; @@ -79,7 +81,6 @@ public static TenantsPermission tenants(TenantsPermission.Action action) { public String toString() { return String.format("Permission", this.action); } - } interface CustomAction { 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 index 24941453f..21597d0fc 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Role.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java @@ -4,10 +4,12 @@ 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> permissions = new ArrayList<>(); 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 index a6f3a7c22..950d4cca4 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -2,9 +2,11 @@ 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; 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 index de02a1dcb..c5002249a 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -2,9 +2,11 @@ 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; diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 9cbc12ade..4cda017c5 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -14,6 +14,7 @@ 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; @@ -74,6 +75,19 @@ public void testGetAll() { assertThat(all.get(1)).returns(viewerRole, Role::getName); } + @Test + public void testGetUserRoles() { + Result> responseCurrent = roles.userRolesGetter().run(); + assertThat(responseCurrent.getError()).as("result had errors").isNull(); + Result> responseAdminUser = roles.userRolesGetter().withUser(adminUser).run(); + assertThat(responseAdminUser.getError()).as("result had errors").isNull(); + + List currentRoles = responseCurrent.getResult(); + List adminRoles = responseAdminUser.getResult(); + + Assertions.assertArrayEquals(currentRoles.toArray(), adminRoles.toArray(), "expect same set of roles"); + } + // TODO: check if I can create a role with a name that's not a valid URL // paramter @@ -109,7 +123,7 @@ public void testCreate() { for (int i = 0; i < wantPermissions.length; i++) { Permission perm = wantPermissions[i]; - assertTrue("should have permission " + perm, hasPermission(myRole, perm)); + assertTrue("should have permission " + perm, checkHasPermission(myRole, perm)); } } finally { roles.deleter().withName(myRole).run(); @@ -135,7 +149,7 @@ public void testAddPermissions() { assertNull("add-permissions operation error", addResult.getError()); // Assert - assertTrue("should have permission " + toAdd, hasPermission(myRole, toAdd)); + assertTrue("should have permission " + toAdd, checkHasPermission(myRole, toAdd)); } finally { roles.deleter().withName(myRole).run(); assertFalse("should not exist after deletion", checkRoleExists(myRole)); @@ -162,7 +176,7 @@ public void testRemovePermissions() { assertNull("remove-permissions operation error", addResult.getError()); // Assert - assertFalse("should not have permission " + toRemove, hasPermission(myRole, toRemove)); + assertFalse("should not have permission " + toRemove, checkHasPermission(myRole, toRemove)); } finally { roles.deleter().withName(myRole).run(); assertFalse("should not exist after deletion", checkRoleExists(myRole)); @@ -174,16 +188,10 @@ private String roleName(String name) { return String.format("%s-%s", currentTest.getMethodName(), name); } - private boolean hasPermission(String role, Permission> perm) { + private boolean checkHasPermission(String role, Permission> perm) { return roles.permissionChecker().withRole(role).withPermission(perm).run().getResult(); } - private boolean hasPermissionWithAction(List> permissions, String action) { - return permissions.stream() - .filter(perm -> perm.getAction().equals(action)) - .findFirst().isPresent(); - } - private boolean checkRoleExists(String role) { return roles.exists().withName(role).run().getResult(); } From ebb8bdfdeb1b3ced4e955867ce5db4754a63d7f8 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 17:59:36 +0100 Subject: [PATCH 07/40] test: add test for assinging and revoking roles --- .../v1/rbac/api/AssignedUsersGetter.java | 8 +- .../client/rbac/ClientRbacTest.java | 84 ++++++++++++++++--- 2 files changed, 76 insertions(+), 16 deletions(-) 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 index 28b061a90..263d170c4 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java @@ -13,14 +13,14 @@ import io.weaviate.client.base.http.HttpClient; public class AssignedUsersGetter extends BaseClient implements ClientResult> { - private String name; + private String role; public AssignedUsersGetter(HttpClient httpClient, Config config) { super(httpClient, config); } - public AssignedUsersGetter withName(String name) { - this.name = name; + public AssignedUsersGetter withRole(String role) { + this.role = role; return this; } @@ -34,6 +34,6 @@ public Result> run() { } private String path() { - return String.format("/authz/roles/%s/users", this.name); + return String.format("/authz/roles/%s/users", this.role); } } diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 4cda017c5..6f8d098c3 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -1,11 +1,11 @@ package io.weaviate.integration.client.rbac; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.list; 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; @@ -19,7 +19,6 @@ import io.weaviate.client.Config; import io.weaviate.client.WeaviateAuthClient; -import io.weaviate.client.WeaviateClient; import io.weaviate.client.base.Result; import io.weaviate.client.v1.auth.exception.AuthException; import io.weaviate.client.v1.rbac.Roles; @@ -69,7 +68,7 @@ public void testGetAll() { Result> response = roles.allGetter().run(); List all = response.getResult(); - assertThat(response.getError()).as("result had errors").isNull(); + 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); @@ -78,9 +77,9 @@ public void testGetAll() { @Test public void testGetUserRoles() { Result> responseCurrent = roles.userRolesGetter().run(); - assertThat(responseCurrent.getError()).as("result had errors").isNull(); + assertThat(responseCurrent.getError()).as("get roles for current user error").isNull(); Result> responseAdminUser = roles.userRolesGetter().withUser(adminUser).run(); - assertThat(responseAdminUser.getError()).as("result had errors").isNull(); + assertThat(responseAdminUser.getError()).as("get roles for user error").isNull(); List currentRoles = responseCurrent.getResult(); List adminRoles = responseAdminUser.getResult(); @@ -88,6 +87,15 @@ public void testGetUserRoles() { 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 @@ -127,7 +135,7 @@ public void testCreate() { } } finally { roles.deleter().withName(myRole).run(); - assertFalse("should not exist after deletion", checkRoleExists(myRole)); + assertFalse("role should not exist after deletion", checkRoleExists(myRole)); } } @@ -143,16 +151,16 @@ public void testAddPermissions() { assumeTrue(checkRoleExists(myRole), "role should exist after creation"); // Act - Result addResult = roles.permissionAdder().withRole(myRole) + Result response = roles.permissionAdder().withRole(myRole) .withPermissions(toAdd) .run(); - assertNull("add-permissions operation error", addResult.getError()); + assertNull("add-permissions operation error", response.getError()); // Assert assertTrue("should have permission " + toAdd, checkHasPermission(myRole, toAdd)); } finally { roles.deleter().withName(myRole).run(); - assertFalse("should not exist after deletion", checkRoleExists(myRole)); + assertFalse("role should not exist after deletion", checkRoleExists(myRole)); } } @@ -170,16 +178,64 @@ public void testRemovePermissions() { assumeTrue(checkRoleExists(myRole), "role should exist after creation"); // Act - Result addResult = roles.permissionRemover().withRole(myRole) + Result response = roles.permissionRemover().withRole(myRole) .withPermissions(toRemove) .run(); - assertNull("remove-permissions operation error", addResult.getError()); + assertNull("remove-permissions operation error", response.getError()); // Assert assertFalse("should not have permission " + toRemove, checkHasPermission(myRole, toRemove)); } finally { roles.deleter().withName(myRole).run(); - assertFalse("should not exist after deletion", checkRoleExists(myRole)); + assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + } + } + + @Test + public void testRevokeRole() { + String myRole = roleName("VectorOwner"); + try { + // Arrange + roles.creator().withName(myRole) + .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) + .run(); + assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + + 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 { + roles.deleter().withName(myRole).run(); + assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + } + } + + @Test + public void testAssignRole() { + String myRole = roleName("VectorOwner"); + try { + // Arrange + roles.creator().withName(myRole) + .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) + .run(); + assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + 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 { + roles.deleter().withName(myRole).run(); + assertFalse("role should not exist after deletion", checkRoleExists(myRole)); } } @@ -195,4 +251,8 @@ private boolean checkHasPermission(String role, Permission Date: Thu, 23 Jan 2025 18:15:14 +0100 Subject: [PATCH 08/40] test: reduce code duplication --- .../client/rbac/ClientRbacTest.java | 59 ++++++++----------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 6f8d098c3..9ae2e8999 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -116,13 +116,10 @@ public void testCreate() { try { // Arrange - roles.deleter().withName(myRole).run(); + deleteRole(myRole); // Act - roles.creator().withName(myRole) - .withPermissions(wantPermissions) - .run(); - assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + createRole(myRole, wantPermissions); Result response = roles.getter().withName(myRole).run(); Role role = response.getResult(); @@ -134,8 +131,7 @@ public void testCreate() { assertTrue("should have permission " + perm, checkHasPermission(myRole, perm)); } } finally { - roles.deleter().withName(myRole).run(); - assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + deleteRole(myRole); } } @@ -145,10 +141,7 @@ public void testAddPermissions() { Permission toAdd = Permission.cluster(ClusterPermission.Action.READ); try { // Arrange - roles.creator().withName(myRole) - .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) - .run(); - assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); // Act Result response = roles.permissionAdder().withRole(myRole) @@ -159,8 +152,7 @@ public void testAddPermissions() { // Assert assertTrue("should have permission " + toAdd, checkHasPermission(myRole, toAdd)); } finally { - roles.deleter().withName(myRole).run(); - assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + deleteRole(myRole); } } @@ -170,12 +162,9 @@ public void testRemovePermissions() { Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE); try { // Arrange - roles.creator().withName(myRole) - .withPermissions( - Permission.cluster(ClusterPermission.Action.READ), - Permission.tenants(TenantsPermission.Action.DELETE)) - .run(); - assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + createRole(myRole, + Permission.cluster(ClusterPermission.Action.READ), + Permission.tenants(TenantsPermission.Action.DELETE)); // Act Result response = roles.permissionRemover().withRole(myRole) @@ -186,8 +175,7 @@ public void testRemovePermissions() { // Assert assertFalse("should not have permission " + toRemove, checkHasPermission(myRole, toRemove)); } finally { - roles.deleter().withName(myRole).run(); - assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + deleteRole(myRole); } } @@ -196,11 +184,7 @@ public void testRevokeRole() { String myRole = roleName("VectorOwner"); try { // Arrange - roles.creator().withName(myRole) - .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) - .run(); - assumeTrue(checkRoleExists(myRole), "role should exist after creation"); - + createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); roles.assigner().withUser(adminUser).witRoles(myRole).run(); assumeTrue(checkHasRole(adminUser, myRole), adminUser + " should have the assigned role"); @@ -211,8 +195,7 @@ public void testRevokeRole() { // Assert assertFalse("should not have " + myRole + "role", checkHasRole(adminUser, myRole)); } finally { - roles.deleter().withName(myRole).run(); - assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + deleteRole(myRole); } } @@ -221,10 +204,7 @@ public void testAssignRole() { String myRole = roleName("VectorOwner"); try { // Arrange - roles.creator().withName(myRole) - .withPermissions(Permission.tenants(TenantsPermission.Action.DELETE)) - .run(); - assumeTrue(checkRoleExists(myRole), "role should exist after creation"); + createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); assumeFalse(checkHasRole(adminUser, myRole), adminUser + " should not have the new role"); // Act @@ -234,8 +214,7 @@ public void testAssignRole() { // Assert assertTrue("should have " + myRole + "role", checkHasRole(adminUser, myRole)); } finally { - roles.deleter().withName(myRole).run(); - assertFalse("role should not exist after deletion", checkRoleExists(myRole)); + deleteRole(myRole); } } @@ -255,4 +234,16 @@ private boolean checkRoleExists(String role) { 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)); + + } } From 03b697a13193daa851d5b5568a1fa15b4f4d0518 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 18:20:49 +0100 Subject: [PATCH 09/40] test: fix test case -- manage_collections is not currently supported --- .../java/io/weaviate/client/v1/rbac/model/PermissionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index c79720b80..d3278befb 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -58,7 +58,7 @@ public static Object[][] serializationTestCases() { { "collections permission", (Supplier>) () -> collections, - new WeaviatePermission("manage_collections", collections), + new WeaviatePermission("create_collections", collections), }, { "cluster permission", From dc74a3bb70123f414f48613c641b8b44f1d2107a Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 18:21:45 +0100 Subject: [PATCH 10/40] ci: fix expected Weaviate server version --- .../java/io/weaviate/integration/client/WeaviateVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index af44bca34..78f0f885f 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java @@ -6,7 +6,7 @@ public class WeaviateVersion { 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.28.3-f73fcee"; + 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 = "34bb9ae"; From d948aa138006b2006f9a45aa0d60e86d8abd3060 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 18:28:31 +0100 Subject: [PATCH 11/40] chore: delete debug print stmts --- src/main/java/io/weaviate/client/base/BaseClient.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/io/weaviate/client/base/BaseClient.java b/src/main/java/io/weaviate/client/base/BaseClient.java index 2b489947e..a0d58cba9 100644 --- a/src/main/java/io/weaviate/client/base/BaseClient.java +++ b/src/main/java/io/weaviate/client/base/BaseClient.java @@ -46,9 +46,6 @@ private Response sendRequest(String endpoint, Object payload, String method, HttpResponse response = this.sendHttpRequest(endpoint, payload, method); int statusCode = response.getStatusCode(); String responseBody = response.getBody(); - System.out.println("response: " + responseBody); - System.out.println("Status Code: " + String.valueOf(response.getStatusCode())); - if (statusCode < 399) { T body = toResponse(responseBody, classOfT); return new Response<>(statusCode, body, null); @@ -64,9 +61,7 @@ private Response sendRequest(String endpoint, Object payload, String method, protected HttpResponse sendHttpRequest(String endpoint, Object payload, String method) throws Exception { String address = config.getBaseURL() + endpoint; - System.out.println(method + " request: " + address); String json = toJsonString(payload); - System.out.println("with body: " + json); if (method.equals("POST")) { return client.sendPostRequest(address, json); } From 9de1f7ff6dc450063f4b04f14a90afd4c9c9474d Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 23 Jan 2025 18:58:40 +0100 Subject: [PATCH 12/40] ci: fix expected commit hash --- .../java/io/weaviate/integration/client/WeaviateVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index 78f0f885f..75ef83870 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java @@ -8,7 +8,7 @@ public class WeaviateVersion { // to be set according to weaviate docker image 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 = "34bb9ae"; + public static final String EXPECTED_WEAVIATE_GIT_HASH = "f73fcee"; private WeaviateVersion() { } From eb5bf5437601e9805bbb134b1b5be8d3fa7f794e Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 24 Jan 2025 12:01:55 +0100 Subject: [PATCH 13/40] test: update expected error message for invalid tokenizer --- .../integration/client/async/schema/ClientSchemaTest.java | 2 +- .../io/weaviate/integration/client/schema/ClientSchemaTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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); } From c4f8de2c88fd21f45ff621214300ec0b8093e4d1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 24 Jan 2025 16:16:30 +0100 Subject: [PATCH 14/40] test: refactor integration test suite to run parametrized tests for sync/async client --- .../client/rbac/ClientRbacTest.java | 254 +++------------- .../tests/rbac/ClientRbacTestSuite.java | 286 ++++++++++++++++++ 2 files changed, 333 insertions(+), 207 deletions(-) create mode 100644 src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 9ae2e8999..7060fe97e 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -1,249 +1,89 @@ 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"; +import io.weaviate.integration.tests.rbac.ClientRbacTestSuite; +public class ClientRbacTest implements ClientRbacTestSuite.Rbac { 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 ClientRbacTest(Config config, String apiKey) { + try { + this.roles = WeaviateAuthClient.apiKey(config, apiKey).roles(); + } catch (AuthException e) { + throw new RuntimeException(e); + } } - public static Object[][] rolesToCreate() { - return new Object[][] { - }; + @Override + public Result getRole(String role) { + return roles.getter().withName(role).run(); } - /** - * 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); + @Override + public Result> getAll() { + return roles.allGetter().run(); } - @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"); + @Override + public Result> getUserRoles() { + return roles.userRolesGetter().run(); } - 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"); + @Override + public Result> getUserRoles(String user) { + return roles.userRolesGetter().withUser(user).run(); } - // 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); - } + @Override + public Result> getAssignedUsers(String role) { + return roles.assignedUsersGetter().withRole(role).run(); } - @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); - } + @Override + public void createRole(String role, Permission... permissions) { + roles.creator().withName(role).withPermissions(permissions).run(); } - @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); - } + @Override + public void deleteRole(String role) { + roles.deleter().withName(role).run(); } - /** 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); + @Override + public Result hasPermission(String role, Permission perm) { + return roles.permissionChecker().withRole(role).withPermission(perm).run(); } - private boolean checkHasPermission(String role, Permission> perm) { - return roles.permissionChecker().withRole(role).withPermission(perm).run().getResult(); + @Override + public Result exists(String role) { + return roles.exists().withName(role).run(); } - private boolean checkRoleExists(String role) { - return roles.exists().withName(role).run().getResult(); + @Override + public Result addPermissions(String role, Permission... permissions) { + return roles.permissionAdder().withRole(role).withPermissions(permissions).run(); } - private boolean checkHasRole(String user, String role) { - return roles.assignedUsersGetter().withRole(role).run().getResult().contains(user); + @Override + public Result removePermissions(String role, Permission... permissions) { + return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); } - private void createRole(String role, Permission... permissions) { - roles.creator().withName(role).withPermissions(permissions).run(); - assumeTrue(checkRoleExists(role), "role should exist after creation"); - + @Override + public Result assignRoles(String user, String... roles) { + return this.roles.assigner().withUser(user).witRoles(roles).run(); } - private void deleteRole(String role) { - roles.deleter().withName(role).run(); - assertFalse("role should not exist after deletion", checkRoleExists(role)); - + @Override + public Result revokeRoles(String user, String... roles) { + return this.roles.revoker().withUser(user).witRoles(roles).run(); } } diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java new file mode 100644 index 000000000..92f95f112 --- /dev/null +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -0,0 +1,286 @@ +package io.weaviate.integration.tests.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.assumeTrue; + +import java.util.List; +import java.util.function.Supplier; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import com.jparams.junit4.JParamsTestRunner; +import com.jparams.junit4.data.DataMethod; + +import io.weaviate.client.Config; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.exception.AuthException; +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; + +@RunWith(JParamsTestRunner.class) +public class ClientRbacTestSuite { + + private static final String adminRole = "admin"; + private static final String viewerRole = "viewer"; + private static final String adminUser = "john-doe"; + private static final String API_KEY = Weaviate.makeSecret(adminUser); + + @Rule + public TestName currentTest = new TestName(); + + @ClassRule + public static WeaviateDockerCompose compose = WeaviateDockerCompose.rbac(adminUser); + + public static Config config() { + return new Config("http", compose.getHttpHostAddress()); + } + + public static Object[][] clients() { + try { + Object[][] r = new Object[][] { + { + (Supplier) () -> new io.weaviate.integration.client.rbac.ClientRbacTest(config(), API_KEY) }, + }; + return r; + } catch (Exception e) { + System.out.println(e.getMessage()); + return null; + } + } + + /** + * By default the admin user which we use to run the tests + * will have 'admin' and 'viewer' roles. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testGetAll(Supplier rbac) { + Rbac roles = rbac.get(); + Result> response = roles.getAll(); + 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); + } + + /** + * Roles retrieved for "current user" should be identical to the ones + * retrieved for them explicitly (by passing the username). + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testGetUserRoles(Supplier rbac) { + Rbac roles = rbac.get(); + Result> responseCurrent = roles.getUserRoles(); + assertThat(responseCurrent.getError()).as("get roles for current user error").isNull(); + Result> responseAdminUser = roles.getUserRoles(adminUser); + 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"); + } + + /** Admin user should have the admin role assigned to them. */ + public void testGetAssignedUsers(Supplier rbac) { + Rbac roles = rbac.get(); + Result> response = roles.getAssignedUsers(adminRole); + 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 + + /** + * Created role should have all of the permissions it was created with. + * Tests addition and fetching the role to. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testCreate(Supplier rbac) { + Rbac roles = rbac.get(); + 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 + roles.deleteRole(myRole); + + // Act + roles.createRole(myRole, wantPermissions); + + Result response = roles.getRole(myRole); + 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(roles, myRole, perm)); + } + } finally { + roles.deleteRole(myRole); + } + } + + /** + * Role can be extended with new permissions. We do not test the "upsert" + * behavior because it is the server's responsibility. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testAddPermissions(Supplier rbac) { + Rbac roles = rbac.get(); + String myRole = roleName("VectorOwner"); + Permission toAdd = Permission.cluster(ClusterPermission.Action.READ); + try { + // Arrange + roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); + + // Act + Result response = roles.addPermissions(myRole, toAdd); + assertNull("add-permissions operation error", response.getError()); + + // Assert + assertTrue("should have permission " + toAdd, checkHasPermission(roles, myRole, toAdd)); + } finally { + roles.deleteRole(myRole); + } + } + + /** + * Permissions can be removed from a role. + * We do not test the "downsert" behavior, because it is the server's + * responsibility. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testRemovePermissions(Supplier rbac) { + Rbac roles = rbac.get(); + String myRole = roleName("VectorOwner"); + Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE); + try { + // Arrange + roles.createRole(myRole, + Permission.cluster(ClusterPermission.Action.READ), + Permission.tenants(TenantsPermission.Action.DELETE)); + + // Act + Result response = roles.removePermissions(myRole, toRemove); + assertNull("remove-permissions operation error", response.getError()); + + // Assert + assertFalse("should not have permission " + toRemove, checkHasPermission(roles, myRole, toRemove)); + } finally { + roles.deleteRole(myRole); + } + } + + /** User can be assigned a role and the role can be revoked. */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testAssignRevokeRole(Supplier rbac) { + Rbac roles = rbac.get(); + String myRole = roleName("VectorOwner"); + try { + // Arrange + roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); + + // Act: Assign + roles.assignRoles(adminUser, myRole); + assumeTrue(checkHasRole(roles, adminUser, myRole), adminUser + " should have the assigned role"); + + // Act: Revoke + Result response = roles.revokeRoles(adminUser, myRole); + assertNull("revoke operation error", response.getError()); + + // Assert + assertFalse("should not have " + myRole + " role", checkHasRole(roles, adminUser, myRole)); + } finally { + roles.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(Rbac roles, String role, Permission> perm) { + return roles.hasPermission(role, perm).getResult(); + } + + private boolean checkHasRole(Rbac roles, String user, String role) { + return roles.getAssignedUsers(role).getResult().contains(user); + } + + /** + * Sync and async test suits should provide an implementation of this interface. + * This way the test suite can be written once with very little + * boilerplate/overhead. + */ + public interface Rbac { + Result getRole(String role); + + Result> getAll(); + + Result> getUserRoles(); + + Result> getUserRoles(String user); + + Result> getAssignedUsers(String role); + + void createRole(String role, Permission... permissions); + + void deleteRole(String role); + + Result hasPermission(String role, Permission perm); + + Result exists(String role); + + Result addPermissions(String role, Permission... permissions); + + Result removePermissions(String role, Permission... permissions); + + Result assignRoles(String user, String... roles); + + Result revokeRoles(String user, String... roles); + } + +} From 6314949642303cec8b12c0bdd60a7ee828cb2a8a Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 24 Jan 2025 20:43:11 +0100 Subject: [PATCH 15/40] feat: add RBAC to WeaviateAsyncClient --- .../client/v1/async/WeaviateAsyncClient.java | 18 ++- .../weaviate/client/v1/async/rbac/Roles.java | 93 +++++++++++++++ .../async/rbac/api/AssignedUsersGetter.java | 51 +++++++++ .../v1/async/rbac/api/PermissionAdder.java | 53 +++++++++ .../v1/async/rbac/api/PermissionChecker.java | 41 +++++++ .../v1/async/rbac/api/PermissionRemover.java | 53 +++++++++ .../v1/async/rbac/api/RoleAllGetter.java | 46 ++++++++ .../v1/async/rbac/api/RoleAssigner.java | 50 ++++++++ .../client/v1/async/rbac/api/RoleCreator.java | 42 +++++++ .../client/v1/async/rbac/api/RoleDeleter.java | 30 +++++ .../client/v1/async/rbac/api/RoleExists.java | 50 ++++++++ .../client/v1/async/rbac/api/RoleGetter.java | 44 +++++++ .../client/v1/async/rbac/api/RoleRevoker.java | 51 +++++++++ .../v1/async/rbac/api/UserRolesGetter.java | 58 ++++++++++ .../client/v1/rbac/api/UserRolesGetter.java | 8 +- .../client/v1/rbac/model/Permission.java | 3 + .../client/async/rbac/ClientRbacTest.java | 108 ++++++++++++++++++ .../tests/rbac/ClientRbacTestSuite.java | 11 +- 18 files changed, 791 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/Roles.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/AssignedUsersGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleExists.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java create mode 100644 src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java create mode 100644 src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java diff --git a/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java b/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java index f8814806c..e01573607 100644 --- a/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java +++ b/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java @@ -1,5 +1,11 @@ package io.weaviate.client.v1.async; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.io.CloseMode; + import io.weaviate.client.Config; import io.weaviate.client.base.Result; import io.weaviate.client.base.http.async.AsyncHttpClient; @@ -13,13 +19,10 @@ import io.weaviate.client.v1.async.data.Data; import io.weaviate.client.v1.async.graphql.GraphQL; import io.weaviate.client.v1.async.misc.Misc; +import io.weaviate.client.v1.async.rbac.Roles; import io.weaviate.client.v1.async.schema.Schema; import io.weaviate.client.v1.auth.provider.AccessTokenProvider; import io.weaviate.client.v1.misc.model.Meta; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; -import org.apache.hc.core5.io.CloseMode; public class WeaviateAsyncClient implements AutoCloseable { private final Config config; @@ -72,9 +75,12 @@ public GraphQL graphQL() { return new GraphQL(client, config, tokenProvider); } + public Roles roles() { + return new Roles(client, config, tokenProvider); + } + private DbVersionProvider initDbVersionProvider() { - DbVersionProvider.VersionGetter getter = () -> - Optional.ofNullable(this.getMeta()) + DbVersionProvider.VersionGetter getter = () -> Optional.ofNullable(this.getMeta()) .filter(result -> !result.hasErrors()) .map(result -> result.getResult().getVersion()); diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java b/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java new file mode 100644 index 000000000..5ed14ac27 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java @@ -0,0 +1,93 @@ +package io.weaviate.client.v1.async.rbac; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; + +import io.weaviate.client.Config; +import io.weaviate.client.v1.async.rbac.api.AssignedUsersGetter; +import io.weaviate.client.v1.async.rbac.api.PermissionAdder; +import io.weaviate.client.v1.async.rbac.api.PermissionChecker; +import io.weaviate.client.v1.async.rbac.api.PermissionRemover; +import io.weaviate.client.v1.async.rbac.api.RoleAllGetter; +import io.weaviate.client.v1.async.rbac.api.RoleAssigner; +import io.weaviate.client.v1.async.rbac.api.RoleCreator; +import io.weaviate.client.v1.async.rbac.api.RoleDeleter; +import io.weaviate.client.v1.async.rbac.api.RoleExists; +import io.weaviate.client.v1.async.rbac.api.RoleGetter; +import io.weaviate.client.v1.async.rbac.api.RoleRevoker; +import io.weaviate.client.v1.async.rbac.api.UserRolesGetter; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Roles { + + private final CloseableHttpAsyncClient client; + private final Config config; + private final AccessTokenProvider tokenProvider; + + public RoleCreator creator() { + return new RoleCreator(client, config, tokenProvider); + } + + /** Get all existing roles. */ + public RoleDeleter deleter() { + return new RoleDeleter(client, config, tokenProvider); + } + + /** + * 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(client, config, tokenProvider); + } + + /** + * 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(client, config, tokenProvider); + } + + /** Check if a role has a permission. */ + public PermissionChecker permissionChecker() { + return new PermissionChecker(client, config, tokenProvider); + } + + /** Get all existing roles. */ + public RoleAllGetter allGetter() { + return new RoleAllGetter(client, config, tokenProvider); + }; + + /** Get role and its assiciated permissions. */ + public RoleGetter getter() { + return new RoleGetter(client, config, tokenProvider); + }; + + /** Get roles assigned to a user. */ + public UserRolesGetter userRolesGetter() { + return new UserRolesGetter(client, config, tokenProvider); + }; + + /** Get users assigned to a role. */ + public AssignedUsersGetter assignedUsersGetter() { + return new AssignedUsersGetter(client, config, tokenProvider); + }; + + /** Check if a role exists. */ + public RoleExists exists() { + return new RoleExists(client, config, tokenProvider); + } + + public RoleAssigner assigner() { + return new RoleAssigner(client, config, tokenProvider); + } + + public RoleRevoker revoker() { + return new RoleRevoker(client, config, tokenProvider); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/AssignedUsersGetter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/AssignedUsersGetter.java new file mode 100644 index 000000000..f523b8069 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/AssignedUsersGetter.java @@ -0,0 +1,51 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Response; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.http.async.ResponseParser; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; + +public class AssignedUsersGetter extends AsyncBaseClient> implements AsyncClientResult> { + private String role; + + public AssignedUsersGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public AssignedUsersGetter withRole(String role) { + this.role = role; + return this; + } + + @Override + public Future>> run(FutureCallback>> callback) { + return sendGetRequest(path(), callback, new ResponseParser>() { + @Override + public Result> parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, 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/async/rbac/api/PermissionAdder.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java new file mode 100644 index 000000000..13765fb8f --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java @@ -0,0 +1,53 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import io.weaviate.client.v1.rbac.model.Permission; +import lombok.AllArgsConstructor; + +public class PermissionAdder extends AsyncBaseClient implements AsyncClientResult { + private String role; + private List> permissions = new ArrayList<>(); + + public PermissionAdder(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public PermissionAdder withRole(String name) { + this.role = name; + return this; + } + + public PermissionAdder withPermissions(Permission... permissions) { + this.permissions = Arrays.asList(permissions); + return this; + } + + /** The API signature for this method is { "permissions": [...] } */ + @AllArgsConstructor + private static class Body { + public final List permissions; + } + + @Override + public Future> run(FutureCallback> callback) { + List permissions = WeaviatePermission.mergePermissions(this.permissions); + return sendPostRequest(path(), new Body(permissions), Void.class, callback); + } + + private String path() { + return String.format("/authz/roles/%s/add-permissions", this.role); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java new file mode 100644 index 000000000..5ae0a21cd --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java @@ -0,0 +1,41 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.model.Permission; + +public class PermissionChecker extends AsyncBaseClient implements AsyncClientResult { + private String role; + private Permission permission; + + public PermissionChecker(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public PermissionChecker withRole(String role) { + this.role = role; + return this; + } + + public PermissionChecker withPermission(Permission permission) { + this.permission = permission; + return this; + } + + @Override + public Future> run(FutureCallback> callback) { + return sendPostRequest(path(), permission.toWeaviate(), Boolean.class, callback); + } + + private String path() { + return String.format("/authz/roles/%s/has-permission", this.role); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java new file mode 100644 index 000000000..32f3f37e6 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java @@ -0,0 +1,53 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import io.weaviate.client.v1.rbac.model.Permission; +import lombok.AllArgsConstructor; + +public class PermissionRemover extends AsyncBaseClient implements AsyncClientResult { + private String role; + private List> permissions = new ArrayList<>(); + + public PermissionRemover(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public PermissionRemover withRole(String role) { + this.role = role; + return this; + } + + public PermissionRemover withPermissions(Permission... permissions) { + this.permissions = Arrays.asList(permissions); + return this; + } + + /** The API signature for this method is { "permissions": [...] } */ + @AllArgsConstructor + private static class Body { + public final List permissions; + } + + @Override + public Future> run(FutureCallback> callback) { + List permissions = WeaviatePermission.mergePermissions(this.permissions); + return sendPostRequest(path(), new Body(permissions), Void.class, callback); + } + + private String path() { + return String.format("/authz/roles/%s/remove-permissions", this.role); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java new file mode 100644 index 000000000..26110ebcf --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java @@ -0,0 +1,46 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Response; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.http.async.ResponseParser; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviateRole; +import io.weaviate.client.v1.rbac.model.Role; + +public class RoleAllGetter extends AsyncBaseClient> implements AsyncClientResult> { + + public RoleAllGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + @Override + public Future>> run(FutureCallback>> callback) { + return sendGetRequest("/authz/roles", callback, new ResponseParser>() { + @Override + public Result> parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, 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/async/rbac/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java new file mode 100644 index 000000000..f0657945e --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java @@ -0,0 +1,50 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import lombok.AllArgsConstructor; + +public class RoleAssigner extends AsyncBaseClient implements AsyncClientResult { + private String user; + private List roles = new ArrayList<>(); + + public RoleAssigner(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + 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 Future> run(FutureCallback> callback) { + return sendPostRequest(path(), new Body(this.roles), Void.class, callback); + } + + private String path() { + return String.format("/authz/users/%s/assign", this.user); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java new file mode 100644 index 000000000..e16685a81 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java @@ -0,0 +1,42 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviateRole; +import io.weaviate.client.v1.rbac.model.Permission; + +public class RoleCreator extends AsyncBaseClient implements AsyncClientResult { + private String name; + private List> permissions = new ArrayList<>(); + + public RoleCreator(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public RoleCreator withName(String name) { + this.name = name; + return this; + } + + public RoleCreator withPermissions(Permission... permissions) { + this.permissions = Arrays.asList(permissions); + return this; + } + + @Override + public Future> run(FutureCallback> callback) { + WeaviateRole role = new WeaviateRole(this.name, this.permissions); + return sendPostRequest("/authz/roles", role, Void.class, callback); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java new file mode 100644 index 000000000..e00e176bd --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java @@ -0,0 +1,30 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; + +public class RoleDeleter extends AsyncBaseClient implements AsyncClientResult { + private String name; + + public RoleDeleter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public RoleDeleter withName(String name) { + this.name = name; + return this; + } + + @Override + public Future> run(FutureCallback> callback) { + return sendDeleteRequest("/authz/roles/" + this.name, null, Void.class, callback); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleExists.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleExists.java new file mode 100644 index 000000000..1cd1dd71b --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleExists.java @@ -0,0 +1,50 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.HttpStatus; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.WeaviateError; +import io.weaviate.client.base.WeaviateErrorResponse; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.model.Role; + +public class RoleExists extends AsyncBaseClient implements AsyncClientResult { + private final RoleGetter getter; + + public RoleExists(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + this.getter = new RoleGetter(httpClient, config, tokenProvider); + } + + public RoleExists withName(String name) { + this.getter.withName(name); + return this; + } + + @Override + public Future> run(FutureCallback> callback) { + return CompletableFuture.supplyAsync(() -> { + try { + Result resp = this.getter.run().get(); + 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); + } catch (InterruptedException | ExecutionException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleGetter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleGetter.java new file mode 100644 index 000000000..eaee97721 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleGetter.java @@ -0,0 +1,44 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.Optional; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Response; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.http.async.ResponseParser; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviateRole; +import io.weaviate.client.v1.rbac.model.Role; + +public class RoleGetter extends AsyncBaseClient implements AsyncClientResult { + private String name; + + public RoleGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + public RoleGetter withName(String name) { + this.name = name; + return this; + } + + @Override + public Future> run(FutureCallback> callback) { + return sendGetRequest("/authz/roles/" + this.name, callback, new ResponseParser() { + @Override + public Result parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, 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/async/rbac/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java new file mode 100644 index 000000000..bf21ad49c --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java @@ -0,0 +1,51 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import lombok.AllArgsConstructor; + +public class RoleRevoker extends AsyncBaseClient implements AsyncClientResult { + private String user; + private List roles = new ArrayList<>(); + + public RoleRevoker(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + 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 Future> run(FutureCallback> callback) { + return sendPostRequest(path(), new Body(this.roles), Void.class, callback); + } + + private String path() { + return String.format("/authz/users/%s/revoke", this.user); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java new file mode 100644 index 000000000..c405e886a --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java @@ -0,0 +1,58 @@ +package io.weaviate.client.v1.async.rbac.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Response; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.http.async.ResponseParser; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.rbac.api.WeaviateRole; +import io.weaviate.client.v1.rbac.model.Role; + +public class UserRolesGetter extends AsyncBaseClient> implements AsyncClientResult> { + private String user; + + public UserRolesGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + /** Leave unset to fetch roles assigned to the current user. */ + public UserRolesGetter withUser(String user) { + this.user = user; + return this; + } + + @Override + public Future>> run(FutureCallback>> callback) { + String path = this.user == null ? "/authz/users/own-roles" : this.path(); + return sendGetRequest(path, callback, new ResponseParser>() { + @Override + public Result> parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, 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/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java index 5ee7fa2c6..87eaedb6c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java @@ -28,12 +28,8 @@ public UserRolesGetter withUser(String user) { @Override public Result> run() { - Response resp; - if (this.user == null) { - resp = sendGetRequest("/authz/users/own-roles", WeaviateRole[].class); - } else { - resp = sendGetRequest(path(), WeaviateRole[].class); - } + String path = this.user == null ? "/authz/users/own-roles" : this.path(); + Response resp = sendGetRequest(path, WeaviateRole[].class); List roles = Optional.ofNullable(resp.getBody()) .map(Arrays::asList) .orElse(new ArrayList<>()) 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 index 76b1616f3..adcbdb9f1 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -41,6 +41,9 @@ public static Permission fromWeaviate(WeaviatePermission perm) { return null; } + // TODO: add overloaded methods for creating multiple permissions (actions) for + // the same collection/tenant/user etc. + public static BackupsPermission backups(BackupsPermission.Action action, String collection) { return new BackupsPermission(action, collection); } diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java new file mode 100644 index 000000000..fd7c08355 --- /dev/null +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -0,0 +1,108 @@ +package io.weaviate.integration.client.async.rbac; + +import java.util.List; +import java.util.concurrent.Callable; + +import io.weaviate.client.Config; +import io.weaviate.client.WeaviateAuthClient; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.async.rbac.Roles; +import io.weaviate.client.v1.auth.exception.AuthException; +import io.weaviate.client.v1.rbac.model.Permission; +import io.weaviate.client.v1.rbac.model.Role; +import io.weaviate.integration.tests.rbac.ClientRbacTestSuite; + +/** + * ClientRbacTest is a {@link ClientRbacTestSuite.Rbac} implementation and a + * wrapper around WeaviateAsyncClient.Roles client which allows the latter to + * be used in the ClientRbacTestSuite. + */ +public class ClientRbacTest implements ClientRbacTestSuite.Rbac { + private Roles roles; + + public ClientRbacTest(Config config, String apiKey) { + try { + this.roles = WeaviateAuthClient.apiKey(config, apiKey).async().roles(); + } catch (AuthException e) { + throw new RuntimeException(e); + } + } + + /** + * Rethrow any exception as a RuntimeException to allow calling AsyncClient + * methods without clashing with {@link ClientRbacTestSuite.Rbac} method + * signatures. + */ + private T rethrow(Callable c) { + try { + return c.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public Result getRole(String role) { + return rethrow(() -> roles.getter().withName(role).run().get()); + } + + @Override + public Result> getAll() { + return rethrow(() -> roles.allGetter().run().get()); + } + + @Override + public Result> getUserRoles() { + return rethrow(() -> roles.userRolesGetter().run().get()); + } + + @Override + public Result> getUserRoles(String user) { + return rethrow(() -> roles.userRolesGetter().withUser(user).run().get()); + } + + @Override + public Result> getAssignedUsers(String role) { + return rethrow(() -> roles.assignedUsersGetter().withRole(role).run().get()); + } + + @Override + public void createRole(String role, Permission... permissions) { + rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run().get()); + } + + @Override + public void deleteRole(String role) { + rethrow(() -> roles.deleter().withName(role).run().get()); + } + + @Override + public Result hasPermission(String role, Permission perm) { + return rethrow(() -> roles.permissionChecker().withRole(role).withPermission(perm).run().get()); + } + + @Override + public Result exists(String role) { + return rethrow(() -> roles.exists().withName(role).run().get()); + } + + @Override + public Result addPermissions(String role, Permission... permissions) { + return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run().get()); + } + + @Override + public Result removePermissions(String role, Permission... permissions) { + return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); + } + + @Override + public Result assignRoles(String user, String... roles) { + return rethrow(() -> this.roles.assigner().withUser(user).witRoles(roles).run().get()); + } + + @Override + public Result revokeRoles(String user, String... roles) { + return rethrow(() -> this.roles.revoker().withUser(user).witRoles(roles).run().get()); + } +} diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 92f95f112..28f8fa691 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -22,7 +22,6 @@ import io.weaviate.client.Config; import io.weaviate.client.base.Result; -import io.weaviate.client.v1.auth.exception.AuthException; import io.weaviate.client.v1.rbac.model.BackupsPermission; import io.weaviate.client.v1.rbac.model.ClusterPermission; import io.weaviate.client.v1.rbac.model.CollectionsPermission; @@ -56,14 +55,12 @@ public static Config config() { public static Object[][] clients() { try { - Object[][] r = new Object[][] { - { - (Supplier) () -> new io.weaviate.integration.client.rbac.ClientRbacTest(config(), API_KEY) }, + return new Object[][] { + { (Supplier) () -> new io.weaviate.integration.client.rbac.ClientRbacTest(config(), API_KEY) }, + { (Supplier) () -> new io.weaviate.integration.client.async.rbac.ClientRbacTest(config(), API_KEY) } }; - return r; } catch (Exception e) { - System.out.println(e.getMessage()); - return null; + throw new RuntimeException(e); } } From 8133383a7cc6c2266e58986c7accd277cef09772 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 27 Jan 2025 16:10:30 +0100 Subject: [PATCH 16/40] feat: add overloaded factory methods for multi-action permissions Makes creating several permissions for the same resource easier. For example, instead of 'Permission.data('Pizza', CREATE), Permission.data('Pizza', DELETE)' users can write 'Permission.data('Pizza', CREATE, DELETE)'. To avoid having ~4 different withPermission() builder methods in the client classes all factory methods on Permission class will return Permission[] (even if known to be length 1). --- .../v1/async/rbac/api/PermissionAdder.java | 9 ++ .../v1/async/rbac/api/PermissionRemover.java | 9 ++ .../client/v1/async/rbac/api/RoleCreator.java | 9 ++ .../client/v1/rbac/api/PermissionAdder.java | 9 ++ .../client/v1/rbac/api/PermissionRemover.java | 9 ++ .../client/v1/rbac/api/RoleCreator.java | 9 ++ .../v1/rbac/model/BackupsPermission.java | 6 +- .../v1/rbac/model/CollectionsPermission.java | 10 +-- .../client/v1/rbac/model/DataPermission.java | 10 +-- .../client/v1/rbac/model/NodesPermission.java | 16 ++-- .../client/v1/rbac/model/Permission.java | 74 +++++++++------ .../client/v1/rbac/model/RolesPermission.java | 6 +- .../client/v1/rbac/model/PermissionTest.java | 76 +++++++++++++--- .../client/async/rbac/ClientRbacTest.java | 15 ++++ .../client/rbac/ClientRbacTest.java | 15 ++++ .../tests/rbac/ClientRbacTestSuite.java | 90 +++++++++++++++++-- 16 files changed, 299 insertions(+), 73 deletions(-) diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java index 13765fb8f..c84fef1e9 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java @@ -35,6 +35,15 @@ public PermissionAdder withPermissions(Permission... permissions) { return this; } + public PermissionAdder withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + /** The API signature for this method is { "permissions": [...] } */ @AllArgsConstructor private static class Body { diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java index 32f3f37e6..d0c3afc1f 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java @@ -35,6 +35,15 @@ public PermissionRemover withPermissions(Permission... permissions) { return this; } + public PermissionRemover withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + /** The API signature for this method is { "permissions": [...] } */ @AllArgsConstructor private static class Body { diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java index e16685a81..b2393f49c 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java @@ -34,6 +34,15 @@ public RoleCreator withPermissions(Permission... permissions) { return this; } + public RoleCreator withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + @Override public Future> run(FutureCallback> callback) { WeaviateRole role = new WeaviateRole(this.name, this.permissions); 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 index e98c5b65f..0c86c3c84 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java @@ -30,6 +30,15 @@ public PermissionAdder withPermissions(Permission... permissions) { return this; } + public PermissionAdder withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + @AllArgsConstructor private static class Body { public final List permissions; 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 index 2d76eeab3..25a88a9aa 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java @@ -30,6 +30,15 @@ public PermissionRemover withPermissions(Permission... permissions) { return this; } + public PermissionRemover withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + @AllArgsConstructor private static class Body { public final List permissions; 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 index 66078f9b5..149577aa8 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java @@ -29,6 +29,15 @@ public RoleCreator withPermissions(Permission... permissions) { return this; } + public RoleCreator withPermissions(Permission[]... permissions) { + List> all = new ArrayList<>(); + for (Permission[] perm : permissions) { + all.addAll(Arrays.asList(perm)); + } + this.permissions = all; + return this; + } + @Override public Result run() { WeaviateRole role = new WeaviateRole(this.name, this.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 index e40bafb5e..9d45a2517 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -10,13 +10,13 @@ public class BackupsPermission extends Permission { final String collection; - public BackupsPermission(Action action, String collection) { + public BackupsPermission(String collection, Action action) { super(action); this.collection = collection; } - BackupsPermission(String action, String collection) { - this(CustomAction.fromString(Action.class, action), collection); + BackupsPermission(String collection, String action) { + this(collection, CustomAction.fromString(Action.class, action)); } @Override 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 index a5b97e8b1..5a2600c8f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -11,15 +11,15 @@ public class CollectionsPermission extends Permission { final String collection; final String tenant; - public CollectionsPermission(Action action, String collection) { - this(action, collection, "*"); + public CollectionsPermission(String collection, Action action) { + this(collection, "*", action); } - CollectionsPermission(String action, String collection) { - this(CustomAction.fromString(Action.class, action), collection); + CollectionsPermission(String collection, String action) { + this(collection, CustomAction.fromString(Action.class, action)); } - private CollectionsPermission(Action action, String collection, String tenant) { + private CollectionsPermission(String collection, String tenant, Action action) { super(action); this.collection = collection; this.tenant = tenant; 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 index 700119b39..5cba9175f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -12,15 +12,15 @@ public class DataPermission extends Permission { final String object; final String tenant; - public DataPermission(Action action, String collection) { - this(action, collection, "*", "*"); + public DataPermission(String collection, Action action) { + this(collection, "*", "*", action); } - DataPermission(String action, String collection) { - this(CustomAction.fromString(Action.class, action), collection); + DataPermission(String collection, String action) { + this(collection, CustomAction.fromString(Action.class, action)); } - private DataPermission(Action action, String collection, String object, String tenant) { + private DataPermission(String collection, String object, String tenant, Action action) { super(action); this.collection = collection; this.object = object; 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 index 2d2794f7d..d1d6486e2 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -13,19 +13,21 @@ public class NodesPermission extends Permission { final String collection; final Verbosity verbosity; - public NodesPermission(Action action, Verbosity verbosity) { - this(action, verbosity, "*"); + /** NodesPermission for all collections. */ + public NodesPermission(Verbosity verbosity, Action action) { + this("*", verbosity, action); } - NodesPermission(String action, Verbosity verbosity) { - this(CustomAction.fromString(Action.class, action), verbosity); + NodesPermission(Verbosity verbosity, String action) { + this(verbosity, CustomAction.fromString(Action.class, action)); } - NodesPermission(String action, Verbosity verbosity, String collection) { - this(CustomAction.fromString(Action.class, action), verbosity, collection); + NodesPermission(String collection, Verbosity verbosity, String action) { + this(collection, verbosity, CustomAction.fromString(Action.class, action)); } - public NodesPermission(Action action, Verbosity verbosity, String collection) { + /** NodesPermission for a defined collection. */ + public NodesPermission(String collection, Verbosity verbosity, Action action) { super(action); this.collection = collection; this.verbosity = verbosity; 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 index adcbdb9f1..78e2db035 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -18,25 +18,23 @@ public abstract class Permission

> { public static Permission fromWeaviate(WeaviatePermission perm) { String action = perm.getAction(); if (perm.getBackups() != null) { - return new BackupsPermission(action, perm.getBackups().getCollection()); + return new BackupsPermission(perm.getBackups().getCollection(), action); } else if (perm.getCollections() != null) { - return new CollectionsPermission(action, perm.getCollections().getCollection()); + return new CollectionsPermission(perm.getCollections().getCollection(), action); } else if (perm.getData() != null) { - return new DataPermission(action, perm.getData().getCollection()); + return new DataPermission(perm.getData().getCollection(), action); } 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(nodes.getCollection(), nodes.getVerbosity(), action); } - return new NodesPermission(action, perm.getNodes().getVerbosity()); + return new NodesPermission(nodes.getVerbosity(), action); } else if (perm.getRoles() != null) { - return new RolesPermission(action, perm.getRoles().getRole()); + return new RolesPermission(perm.getRoles().getRole(), action); } 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; } @@ -44,42 +42,62 @@ public static Permission fromWeaviate(WeaviatePermission perm) { // TODO: add overloaded methods for creating multiple permissions (actions) for // the same collection/tenant/user etc. - public static BackupsPermission backups(BackupsPermission.Action action, String collection) { - return new BackupsPermission(action, collection); + public static BackupsPermission[] backups(BackupsPermission.Action action, String collection) { + return new BackupsPermission[] { new BackupsPermission(collection, action) }; } - public static ClusterPermission cluster(ClusterPermission.Action action) { - return new ClusterPermission(action); + public static ClusterPermission[] cluster(ClusterPermission.Action action) { + return new ClusterPermission[] { new ClusterPermission(action) }; } - public static CollectionsPermission collections(CollectionsPermission.Action action, String collection) { - return new CollectionsPermission(action, collection); + public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action action) { + return new CollectionsPermission[] { new CollectionsPermission(collection, action) }; } - public static DataPermission data(DataPermission.Action action, String collection) { - return new DataPermission(action, collection); + public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action... actions) { + CollectionsPermission[] permissions = new CollectionsPermission[actions.length]; + for (int i = 0; i < actions.length; i++) { + permissions[i] = new CollectionsPermission(collection, actions[i]); + } + return permissions; + } + + public static DataPermission[] data(String collection, DataPermission.Action action) { + return new DataPermission[] { new DataPermission(collection, action) }; + } + + public static DataPermission[] data(String collection, DataPermission.Action... actions) { + DataPermission[] permissions = new DataPermission[actions.length]; + for (int i = 0; i < actions.length; i++) { + permissions[i] = new DataPermission(collection, actions[i]); + } + return permissions; } - public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity) { - return new NodesPermission(action, verbosity); + public static NodesPermission[] nodes(NodesPermission.Verbosity verbosity, NodesPermission.Action action) { + return new NodesPermission[] { new NodesPermission(verbosity, action) }; } - public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity, - String collection) { - return new NodesPermission(action, verbosity, collection); + public static NodesPermission[] nodes(String collection, NodesPermission.Verbosity verbosity, + NodesPermission.Action action) { + return new NodesPermission[] { new NodesPermission(collection, verbosity, action) }; } - public static RolesPermission roles(RolesPermission.Action action, String role) { - return new RolesPermission(action, role); + public static RolesPermission[] roles(String role, RolesPermission.Action action) { + return new RolesPermission[] { new RolesPermission(role, action) }; } - public static TenantsPermission tenants(TenantsPermission.Action action) { - return new TenantsPermission(action); + public static RolesPermission[] roles(String role, RolesPermission.Action... actions) { + RolesPermission[] permissions = new RolesPermission[actions.length]; + for (int i = 0; i < actions.length; i++) { + permissions[i] = new RolesPermission(role, actions[i]); + } + return permissions; } - // public static UsersPermission users(UsersPermission.Action action) { - // return new UsersPermission(action); - // } + public static TenantsPermission[] tenants(TenantsPermission.Action action) { + return new TenantsPermission[] { new TenantsPermission(action) }; + } public String toString() { return String.format("Permission", this.action); 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 index 950d4cca4..7d7efa6eb 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -10,13 +10,13 @@ public class RolesPermission extends Permission { final String role; - public RolesPermission(Action action, String role) { + public RolesPermission(String role, Action action) { super(action); this.role = role; } - RolesPermission(String action, String role) { - this(CustomAction.fromString(Action.class, action), role); + RolesPermission(String role, String action) { + this(role, CustomAction.fromString(Action.class, action)); } @Override 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 index d3278befb..4ba966300 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -1,8 +1,10 @@ package io.weaviate.client.v1.rbac.model; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.util.ArrayList; +import java.util.Arrays; import java.util.function.Supplier; import org.junit.Test; @@ -20,21 +22,15 @@ @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"); + BackupsPermission backups = new BackupsPermission("Pizza", BackupsPermission.Action.MANAGE); + DataPermission data = new DataPermission("Pizza", DataPermission.Action.MANAGE); + NodesPermission nodes = new NodesPermission("Pizza", Verbosity.MINIMAL, NodesPermission.Action.READ); + RolesPermission roles = new RolesPermission("TestWriter", RolesPermission.Action.MANAGE); + CollectionsPermission collections = new CollectionsPermission("Pizza", CollectionsPermission.Action.CREATE); 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, @@ -87,7 +83,7 @@ private static Matcher sameAs(T expected) { @Test public void testDefaultDataPermission() { - DataPermission perm = new DataPermission(DataPermission.Action.MANAGE, "Pizza"); + DataPermission perm = new DataPermission("Pizza", DataPermission.Action.MANAGE); assertThat(perm).as("data permission must have object=* and tenant=*") .returns("*", DataPermission::getObject) .returns("*", DataPermission::getTenant); @@ -95,14 +91,14 @@ public void testDefaultDataPermission() { @Test public void testDefaultCollectionsPermission() { - CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.CREATE, "Pizza"); + CollectionsPermission perm = new CollectionsPermission("Pizza", CollectionsPermission.Action.CREATE); 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); + NodesPermission perm = new NodesPermission(NodesPermission.Verbosity.MINIMAL, NodesPermission.Action.READ); assertThat(perm).as("nodes permission should affect all collections if one is not specified") .returns("*", NodesPermission::getCollection); } @@ -119,8 +115,60 @@ public void testDefaultTenantsPermission() { public void testFromWeaviate(String name, Supplier> expectedFunc, WeaviatePermission input) throws Exception { + System.out.println("fromWeaviate: " + name); Permission expected = expectedFunc.get(); Permission actual = Permission.fromWeaviate(input); MatcherAssert.assertThat(name, actual, sameAs(expected)); } + + /** + * groupedConstructors returns test cases for overloaded factory methods, which + * allow creating multiple permission entries for the same resource. + * + * Permission types which only have 1 possible action (e.g. backup/cluster + * permissions) are omitted. + */ + public static Object[][] groupedConstructors() { + return new Object[][] { + { + Permission.collections("Pizza", + CollectionsPermission.Action.CREATE, + CollectionsPermission.Action.READ, + CollectionsPermission.Action.DELETE), + new String[] { + "create_collections", + "read_collections", + "delete_collections", + }, + }, + { + Permission.data("Pizza", + DataPermission.Action.CREATE, + DataPermission.Action.READ, + DataPermission.Action.DELETE), + new String[] { + "create_data", + "read_data", + "delete_data", + }, + }, + { + Permission.roles("TestRole", + RolesPermission.Action.READ, + RolesPermission.Action.MANAGE), + new String[] { + "read_roles", + "manage_roles", + }, + }, + }; + } + + @DataMethod(source = PermissionTest.class, method = "groupedConstructors") + @Test + public void testGroupedConstructors(Permission>[] permissions, String[] expectedActions) { + Arrays.stream(permissions).map(Permission::getAction).forEach(a -> System.out.println(a)); + Object[] actualActions = Arrays.stream(permissions).map(Permission::getAction).toArray(); + assertArrayEquals(expectedActions, actualActions, "set of allowed actions do not match"); + } } diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java index fd7c08355..6233fdb7b 100644 --- a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -71,6 +71,11 @@ public void createRole(String role, Permission... permissions) { rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run().get()); } + @Override + public void createRole(String role, Permission[]... permissions) { + rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run().get()); + } + @Override public void deleteRole(String role) { rethrow(() -> roles.deleter().withName(role).run().get()); @@ -91,11 +96,21 @@ public Result addPermissions(String role, Permission... permissions) { return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run().get()); } + @Override + public Result addPermissions(String role, Permission[]... permissions) { + return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run().get()); + } + @Override public Result removePermissions(String role, Permission... permissions) { return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); } + @Override + public Result removePermissions(String role, Permission[]... permissions) { + return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); + } + @Override public Result assignRoles(String user, String... roles) { return rethrow(() -> this.roles.assigner().withUser(user).witRoles(roles).run().get()); diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 7060fe97e..fc0c99c0d 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -52,6 +52,11 @@ public void createRole(String role, Permission... permissions) { roles.creator().withName(role).withPermissions(permissions).run(); } + @Override + public void createRole(String role, Permission[]... permissions) { + roles.creator().withName(role).withPermissions(permissions).run(); + } + @Override public void deleteRole(String role) { roles.deleter().withName(role).run(); @@ -72,11 +77,21 @@ public Result addPermissions(String role, Permission... permissions) { return roles.permissionAdder().withRole(role).withPermissions(permissions).run(); } + @Override + public Result addPermissions(String role, Permission[]... permissions) { + return roles.permissionAdder().withRole(role).withPermissions(permissions).run(); + } + @Override public Result removePermissions(String role, Permission... permissions) { return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); } + @Override + public Result removePermissions(String role, Permission[]... permissions) { + return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); + } + @Override public Result assignRoles(String user, String... roles) { return this.roles.assigner().withUser(user).witRoles(roles).run(); diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 28f8fa691..d54a40040 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -125,13 +125,13 @@ public void testCreate(Supplier rbac) { String myRole = roleName("VectorOwner"); String myCollection = "Pizza"; - Permission[] wantPermissions = new Permission[] { + 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.nodes(myCollection, Verbosity.MINIMAL, NodesPermission.Action.READ), + Permission.roles(viewerRole, RolesPermission.Action.MANAGE), + Permission.collections(myCollection, CollectionsPermission.Action.CREATE), + Permission.data(myCollection, DataPermission.Action.UPDATE), Permission.tenants(TenantsPermission.Action.DELETE), }; @@ -148,7 +148,7 @@ public void testCreate(Supplier rbac) { assertThat(role).as("wrong role name").returns(myRole, Role::getName); for (int i = 0; i < wantPermissions.length; i++) { - Permission perm = wantPermissions[i]; + Permission perm = wantPermissions[i][0]; // We create each permission group with only 1 action assertTrue("should have permission " + perm, checkHasPermission(roles, myRole, perm)); } } finally { @@ -165,7 +165,7 @@ public void testCreate(Supplier rbac) { public void testAddPermissions(Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission toAdd = Permission.cluster(ClusterPermission.Action.READ); + Permission toAdd = Permission.cluster(ClusterPermission.Action.READ)[0]; try { // Arrange roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); @@ -181,6 +181,38 @@ public void testAddPermissions(Supplier rbac) { } } + /** + * Check query builder accepts arrays of permissions, + * which is handy in combination with factory methods that create permissions + * with multiple actions. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testAddPermissionsMultipleActions(Supplier rbac) { + Rbac roles = rbac.get(); + String myRole = roleName("VectorOwner"); + Permission[] toAdd = Permission.data("Pizza", + DataPermission.Action.READ, + DataPermission.Action.CREATE); + try { + // Arrange + roles.createRole(myRole, Permission.collections("Pizza", + CollectionsPermission.Action.UPDATE, + CollectionsPermission.Action.DELETE)); + + // Act + Result response = roles.addPermissions(myRole, toAdd); + assertNull("add-permissions operation error", response.getError()); + + // Assert + for (Permission perm : toAdd) { + assertTrue("should have permission " + perm, checkHasPermission(roles, myRole, perm)); + } + } finally { + roles.deleteRole(myRole); + } + } + /** * Permissions can be removed from a role. * We do not test the "downsert" behavior, because it is the server's @@ -191,7 +223,7 @@ public void testAddPermissions(Supplier rbac) { public void testRemovePermissions(Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE); + Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE)[0]; try { // Arrange roles.createRole(myRole, @@ -209,6 +241,42 @@ public void testRemovePermissions(Supplier rbac) { } } + /** + * Check query builder accepts arrays of permissions, + * which is handy in combination with factory methods that create permissions + * with multiple actions. + */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test + public void testRemovePermissionsMultipleAction(Supplier rbac) { + Rbac roles = rbac.get(); + String myRole = roleName("VectorOwner"); + Permission[] toRemove = Permission.data("Pizza", + DataPermission.Action.READ, + DataPermission.Action.CREATE); + try { + // Arrange + roles.createRole(myRole, + Permission.data("Pizza", + DataPermission.Action.READ, + DataPermission.Action.UPDATE, + DataPermission.Action.DELETE, + DataPermission.Action.CREATE), + Permission.tenants(TenantsPermission.Action.DELETE)); + + // Act + Result response = roles.removePermissions(myRole, toRemove); + assertNull("remove-permissions operation error", response.getError()); + + // Assert + for (Permission perm : toRemove) { + assertFalse("should not have permission " + toRemove, checkHasPermission(roles, myRole, perm)); + } + } finally { + roles.deleteRole(myRole); + } + } + /** User can be assigned a role and the role can be revoked. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") @Test @@ -265,6 +333,8 @@ public interface Rbac { void createRole(String role, Permission... permissions); + void createRole(String role, Permission[]... permissions); + void deleteRole(String role); Result hasPermission(String role, Permission perm); @@ -273,8 +343,12 @@ public interface Rbac { Result addPermissions(String role, Permission... permissions); + Result addPermissions(String role, Permission[]... permissions); + Result removePermissions(String role, Permission... permissions); + Result removePermissions(String role, Permission[]... permissions); + Result assignRoles(String user, String... roles); Result revokeRoles(String user, String... roles); From ec123e6e0427179fbb53aa000e1eefcd1a19bbfd Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 27 Jan 2025 22:08:34 +0100 Subject: [PATCH 17/40] test: remove duplicated env variable --- .../io/weaviate/integration/client/WeaviateDockerCompose.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java index 29f5af89f..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; @@ -84,7 +85,6 @@ public Weaviate(String dockerImageName, boolean withOffloadS3, String adminUser) withEnv("AUTHORIZATION_RBAC_ENABLED", "true"); withEnv("AUTHENTICATION_APIKEY_USERS", adminUser); withEnv("AUTHENTICATION_APIKEY_ALLOWED_KEYS", makeSecret(adminUser)); - withEnv("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false"); withEnv("AUTHORIZATION_ADMIN_USERS", adminUser); } From 138e2cec8f6e691fb89533289845f4b0831ea59a Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 30 Jan 2025 09:25:23 +0100 Subject: [PATCH 18/40] refactor: rename CustomAction to RbacAction Moved to a separate file and documented. --- .../v1/rbac/api/WeaviatePermission.java | 1 + .../client/v1/rbac/api/WeaviateRole.java | 2 + .../v1/rbac/model/BackupsPermission.java | 4 +- .../v1/rbac/model/ClusterPermission.java | 4 +- .../v1/rbac/model/CollectionsPermission.java | 4 +- .../client/v1/rbac/model/DataPermission.java | 4 +- .../client/v1/rbac/model/NodesPermission.java | 6 +-- .../client/v1/rbac/model/Permission.java | 33 +++----------- .../client/v1/rbac/model/RbacAction.java | 44 +++++++++++++++++++ .../client/v1/rbac/model/RolesPermission.java | 4 +- .../v1/rbac/model/TenantsPermission.java | 4 +- .../client/v1/rbac/model/UsersPermission.java | 4 +- .../tests/rbac/ClientRbacTestSuite.java | 4 ++ 13 files changed, 74 insertions(+), 44 deletions(-) create mode 100644 src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java 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 index 26d6b159e..98fa65ba6 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -13,6 +13,7 @@ import lombok.Builder; import lombok.Getter; +/** API model for serializing/deserializing permissions. */ @Getter @Builder @AllArgsConstructor 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 index 2675ac724..ae24052a1 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -6,6 +6,7 @@ import io.weaviate.client.v1.rbac.model.Role; import lombok.Getter; +/** API model for serializing/deserializing roles. */ @Getter public class WeaviateRole { String name; @@ -16,6 +17,7 @@ public WeaviateRole(String name, List> permissions) { this.permissions = WeaviatePermission.mergePermissions(permissions); } + /** Create {@link Role} from the API response object. */ public Role toRole() { List> permissions = this.permissions.stream() .>map(perm -> Permission.fromWeaviate(perm)).toList(); 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 index 9d45a2517..a22fd4c15 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -16,7 +16,7 @@ public BackupsPermission(String collection, Action action) { } BackupsPermission(String collection, String action) { - this(collection, CustomAction.fromString(Action.class, action)); + this(collection, RbacAction.fromString(Action.class, action)); } @Override @@ -25,7 +25,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { MANAGE("manage_backups"); @Getter 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 index c70dd7a5f..80899341e 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -13,7 +13,7 @@ public ClusterPermission(Action action) { } ClusterPermission(String action) { - this(CustomAction.fromString(Action.class, action)); + this(RbacAction.fromString(Action.class, action)); } @Override @@ -22,7 +22,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { READ("read_cluster"); @Getter 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 index 5a2600c8f..a6eca347d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -16,7 +16,7 @@ public CollectionsPermission(String collection, Action action) { } CollectionsPermission(String collection, String action) { - this(collection, CustomAction.fromString(Action.class, action)); + this(collection, RbacAction.fromString(Action.class, action)); } private CollectionsPermission(String collection, String tenant, Action action) { @@ -31,7 +31,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { CREATE("create_collections"), READ("read_collections"), UPDATE("update_collections"), 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 index 5cba9175f..8037f8232 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -17,7 +17,7 @@ public DataPermission(String collection, Action action) { } DataPermission(String collection, String action) { - this(collection, CustomAction.fromString(Action.class, action)); + this(collection, RbacAction.fromString(Action.class, action)); } private DataPermission(String collection, String object, String tenant, Action action) { @@ -33,7 +33,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { CREATE("create_data"), READ("read_data"), UPDATE("update_data"), 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 index d1d6486e2..a3df6840a 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -19,11 +19,11 @@ public NodesPermission(Verbosity verbosity, Action action) { } NodesPermission(Verbosity verbosity, String action) { - this(verbosity, CustomAction.fromString(Action.class, action)); + this(verbosity, RbacAction.fromString(Action.class, action)); } NodesPermission(String collection, Verbosity verbosity, String action) { - this(collection, verbosity, CustomAction.fromString(Action.class, action)); + this(collection, verbosity, RbacAction.fromString(Action.class, action)); } /** NodesPermission for a defined collection. */ @@ -39,7 +39,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { READ("read_nodes"); @Getter 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 index 78e2db035..bfa1c2f52 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -9,12 +9,16 @@ public abstract class Permission

> { @Getter final transient String action; - Permission(CustomAction action) { + Permission(RbacAction action) { this.action = action.getValue(); } + /** Convert the permission to {@link WeaviatePermission}. */ public abstract WeaviatePermission toWeaviate(); + /** + * Convert {@link WeaviatePermission} to concrete {@link Permission}. + */ public static Permission fromWeaviate(WeaviatePermission perm) { String action = perm.getAction(); if (perm.getBackups() != null) { @@ -33,15 +37,12 @@ public static Permission fromWeaviate(WeaviatePermission perm) { return new RolesPermission(perm.getRoles().getRole(), action); } else if (perm.getTenants() != null) { return new TenantsPermission(action); - } else if (CustomAction.isValid(ClusterPermission.Action.class, action)) { + } else if (RbacAction.isValid(ClusterPermission.Action.class, action)) { return new ClusterPermission(action); } return null; } - // TODO: add overloaded methods for creating multiple permissions (actions) for - // the same collection/tenant/user etc. - public static BackupsPermission[] backups(BackupsPermission.Action action, String collection) { return new BackupsPermission[] { new BackupsPermission(collection, action) }; } @@ -103,25 +104,3 @@ 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/RbacAction.java b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java new file mode 100644 index 000000000..cd7897105 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java @@ -0,0 +1,44 @@ +package io.weaviate.client.v1.rbac.model; + +/** + * RbacAction is a utility interface to allow retrieving concrete enum values + * from their underlying string representations. + * + *

+ * Usage: + * + *

+ * enum MyAction implements RbacAction {
+ *   FOO("do_foo"),
+ *   BAR("do_bar");
+ *
+ *   @Getter
+ *   private String value;
+ * }
+ * 
+ * + *

+ * Then {@code MyAction.FOO} can be retrieved from "do_foo" using + * {@link #fromString}. + */ +interface RbacAction { + String getValue(); + + static & RbacAction> 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 (RbacAction action : enumClass.getEnumConstants()) { + if (action.getValue().equals(value)) { + return true; + } + } + return false; + } +} 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 index 7d7efa6eb..877ac8789 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -16,7 +16,7 @@ public RolesPermission(String role, Action action) { } RolesPermission(String role, String action) { - this(role, CustomAction.fromString(Action.class, action)); + this(role, RbacAction.fromString(Action.class, action)); } @Override @@ -25,7 +25,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { READ("read_roles"), MANAGE("manage_roles"); 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 index c5002249a..373157188 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -15,7 +15,7 @@ public TenantsPermission(Action action) { } TenantsPermission(String action) { - this(CustomAction.fromString(Action.class, action)); + this(RbacAction.fromString(Action.class, action)); } private TenantsPermission(Action action, String tenant) { @@ -29,7 +29,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { CREATE("create_tenants"), READ("read_tenants"), UPDATE("update_tenants"), 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 index b7758bf80..4639b1779 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -16,7 +16,7 @@ public UsersPermission(Action action) { } UsersPermission(String action) { - this(CustomAction.fromString(Action.class, action)); + this(RbacAction.fromString(Action.class, action)); } @Override @@ -25,7 +25,7 @@ public WeaviatePermission toWeaviate() { } @AllArgsConstructor - public enum Action implements CustomAction { + public enum Action implements RbacAction { MANAGE("manage_users"); @Getter diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index d54a40040..681b03336 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -101,6 +101,8 @@ public void testGetUserRoles(Supplier rbac) { } /** Admin user should have the admin role assigned to them. */ + @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Test public void testGetAssignedUsers(Supplier rbac) { Rbac roles = rbac.get(); Result> response = roles.getAssignedUsers(adminRole); @@ -227,6 +229,8 @@ public void testRemovePermissions(Supplier rbac) { try { // Arrange roles.createRole(myRole, + // Create an extra permission so that the role would not be + // deleted with its otherwise only permission is removed. Permission.cluster(ClusterPermission.Action.READ), Permission.tenants(TenantsPermission.Action.DELETE)); From be0d280afac3a17357a5ebd70b80385cf8988b47 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 30 Jan 2025 10:56:38 +0100 Subject: [PATCH 19/40] test: move RBAC configuration to a separate container class --- .../client/v1/rbac/model/RbacAction.java | 3 +- .../client/WeaviateDockerCompose.java | 44 +------------------ .../client/WeaviateWithRbacContainer.java | 36 +++++++++++++++ .../tests/rbac/ClientRbacTestSuite.java | 16 +++---- 4 files changed, 47 insertions(+), 52 deletions(-) create mode 100644 src/test/java/io/weaviate/integration/client/WeaviateWithRbacContainer.java diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java index cd7897105..15c326dcf 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java @@ -24,7 +24,8 @@ interface RbacAction { String getValue(); - static & RbacAction> E fromString(Class enumClass, String value) { + static & RbacAction> E fromString(Class enumClass, String value) + throws IllegalArgumentException { for (E action : enumClass.getEnumConstants()) { if (action.getValue().equals(value)) { return action; diff --git a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java index 78926d58a..aaa6b5873 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java @@ -18,36 +18,19 @@ public class WeaviateDockerCompose implements TestRule { 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 { @@ -76,27 +59,6 @@ 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 { @@ -138,11 +100,7 @@ public void start() { } contextionary = new Contextionary(); contextionary.start(); - if (adminUser == null) { - weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3); - } else { - weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3, this.adminUser); - } + weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3); weaviate.start(); } diff --git a/src/test/java/io/weaviate/integration/client/WeaviateWithRbacContainer.java b/src/test/java/io/weaviate/integration/client/WeaviateWithRbacContainer.java new file mode 100644 index 000000000..38bac1873 --- /dev/null +++ b/src/test/java/io/weaviate/integration/client/WeaviateWithRbacContainer.java @@ -0,0 +1,36 @@ +package io.weaviate.integration.client; + +import org.testcontainers.weaviate.WeaviateContainer; + +public class WeaviateWithRbacContainer extends WeaviateContainer { + + public WeaviateWithRbacContainer(String dockerImageName, String admin, String... viewers) { + super(dockerImageName); + + withEnv("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false"); + withEnv("AUTHENTICATION_APIKEY_ENABLED", "true"); + withEnv("AUTHORIZATION_RBAC_ENABLED", "true"); + withEnv("AUTHENTICATION_APIKEY_ALLOWED_KEYS", makeSecret(admin)); + withEnv("AUTHENTICATION_APIKEY_USERS", admin); + withEnv("AUTHORIZATION_ADMIN_USERS", admin); + withEnv("PERSISTENCE_DATA_PATH", "./data"); + withEnv("BACKUP_FILESYSTEM_PATH", "/tmp/backups"); + withEnv("ENABLE_MODULES", "backup-filesystem"); + withEnv("CLUSTER_GOSSIP_BIND_PORT", "7100"); + withEnv("CLUSTER_DATA_BIND_PORT", "7101"); + + if (viewers.length > 0) { + withEnv("AUTHORIZATION_VIEWER_USERS", String.join(",", viewers)); + } + } + + /** + * Generate API secret for a username. When running an instance with + * authentication enabled, {@link WeaviateWithRbacContainer} 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"; + } +} diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 681b03336..a2e423528 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.rules.TestName; import org.junit.runner.RunWith; +import org.testcontainers.weaviate.WeaviateContainer; import com.jparams.junit4.JParamsTestRunner; import com.jparams.junit4.data.DataMethod; @@ -32,8 +33,8 @@ 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; +import io.weaviate.integration.client.WeaviateDockerImage; +import io.weaviate.integration.client.WeaviateWithRbacContainer; @RunWith(JParamsTestRunner.class) public class ClientRbacTestSuite { @@ -41,16 +42,18 @@ public class ClientRbacTestSuite { private static final String adminRole = "admin"; private static final String viewerRole = "viewer"; private static final String adminUser = "john-doe"; - private static final String API_KEY = Weaviate.makeSecret(adminUser); + private static final String API_KEY = WeaviateWithRbacContainer.makeSecret(adminUser); @Rule public TestName currentTest = new TestName(); @ClassRule - public static WeaviateDockerCompose compose = WeaviateDockerCompose.rbac(adminUser); + public static WeaviateContainer weaviate = new WeaviateWithRbacContainer( + WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE, + adminUser); public static Config config() { - return new Config("http", compose.getHttpHostAddress()); + return new Config("http", weaviate.getHttpHostAddress()); } public static Object[][] clients() { @@ -113,9 +116,6 @@ public void testGetAssignedUsers(Supplier rbac) { 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 - /** * Created role should have all of the permissions it was created with. * Tests addition and fetching the role to. From bf1d800978ee2fa80a5f9afed5ccf3f6a60aede5 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 30 Jan 2025 16:13:30 +0100 Subject: [PATCH 20/40] chore: fix documentation --- src/main/java/io/weaviate/client/v1/rbac/Roles.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/weaviate/client/v1/rbac/Roles.java b/src/main/java/io/weaviate/client/v1/rbac/Roles.java index a33e6b5a7..c1c0c63e7 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/Roles.java +++ b/src/main/java/io/weaviate/client/v1/rbac/Roles.java @@ -22,11 +22,12 @@ public class Roles { private final HttpClient httpClient; private final Config config; + /** Create a new role. */ public RoleCreator creator() { return new RoleCreator(httpClient, config); } - /** Get all existing roles. */ + /** Delete a role. */ public RoleDeleter deleter() { return new RoleDeleter(httpClient, config); } @@ -60,7 +61,7 @@ public RoleAllGetter allGetter() { return new RoleAllGetter(httpClient, config); }; - /** Get role and its assiciated permissions. */ + /** Get role and its associated permissions. */ public RoleGetter getter() { return new RoleGetter(httpClient, config); }; @@ -80,10 +81,12 @@ 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); } From 84ae4a909785f75ba6a276ffe29b65fec3941abd Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 30 Jan 2025 18:03:42 +0100 Subject: [PATCH 21/40] refactor: return Result instead of Result --- .../java/io/weaviate/client/base/Result.java | 52 +++++++++++++++++-- .../v1/async/rbac/api/PermissionAdder.java | 6 +-- .../v1/async/rbac/api/PermissionRemover.java | 6 +-- .../v1/async/rbac/api/RoleAssigner.java | 6 +-- .../client/v1/async/rbac/api/RoleCreator.java | 6 +-- .../client/v1/async/rbac/api/RoleDeleter.java | 6 +-- .../client/v1/async/rbac/api/RoleRevoker.java | 6 +-- .../client/v1/rbac/api/PermissionAdder.java | 7 +-- .../client/v1/rbac/api/PermissionRemover.java | 6 +-- .../client/v1/rbac/api/RoleAssigner.java | 9 ++-- .../client/v1/rbac/api/RoleCreator.java | 6 +-- .../client/v1/rbac/api/RoleDeleter.java | 6 +-- .../client/v1/rbac/api/RoleRevoker.java | 6 +-- 13 files changed, 87 insertions(+), 41 deletions(-) diff --git a/src/main/java/io/weaviate/client/base/Result.java b/src/main/java/io/weaviate/client/base/Result.java index bdc237acb..c3ebe00dc 100644 --- a/src/main/java/io/weaviate/client/base/Result.java +++ b/src/main/java/io/weaviate/client/base/Result.java @@ -5,6 +5,11 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; + +import io.weaviate.client.base.http.async.ResponseParser; import lombok.AccessLevel; import lombok.Getter; import lombok.ToString; @@ -23,11 +28,13 @@ public Result(Response response) { public Result(int statusCode, T body, WeaviateErrorResponse errors) { if (errors != null && errors.getError() != null) { - List items = errors.getError().stream().filter(Objects::nonNull).collect(Collectors.toList()); + List items = errors.getError().stream().filter(Objects::nonNull) + .collect(Collectors.toList()); this.error = new WeaviateError(statusCode, items); this.result = body; } else if (errors != null && errors.getMessage() != null) { - this.error = new WeaviateError(statusCode, Collections.singletonList(WeaviateErrorMessage.builder().message(errors.getMessage()).build())); + this.error = new WeaviateError(statusCode, + Collections.singletonList(WeaviateErrorMessage.builder().message(errors.getMessage()).build())); this.result = body; } else { this.result = body; @@ -40,12 +47,47 @@ public boolean hasErrors() { } /** - * Copy the Result object with a null body, preserving only the status code and the error message. + * Copy the Result object with a null body, preserving only the status code and + * the error message. * - * @param Would-be response type. It's required for type safety, but can be anything since the body is always set to null. + * @param Would-be response type. It's required for type safety, but can be + * anything since the body is always set to null. * @return A copy of this Result. */ public Result toErrorResult() { - return new Result<>(this.error.getStatusCode(), null, WeaviateErrorResponse.builder().error(this.error.getMessages()).build()); + return new Result<>(this.error.getStatusCode(), null, + WeaviateErrorResponse.builder().error(this.error.getMessages()).build()); + } + + /** + * Convert {@code Result} response to a {@code Result}. + * Returns true if response status code is 200. + * + * @param response Response from a call that does not return a value, like + * {@link BaseClient#sendDeleteRequest}. + * @return {@code Result} + */ + public static Result voidToBoolean(Response response) { + int status = response.getStatusCode(); + return new Result<>(status, status == 200, response.getErrors()); } + + /** + * Get a custom parser to convert {@code Result} response as to a + * {@code Result}. Result is true if response status code is 200. + * + * @param response Response from an async call that does not return a value, + * like {@link AsyncBaseClient#sendDeleteRequest}. + * @return {@code Result} + */ + public static ResponseParser voidToBooleanParser() { + return new ResponseParser() { + @Override + public Result parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, Object.class); + return new Result<>(resp.getStatusCode(), resp.getStatusCode() == HttpStatus.SC_OK, resp.getErrors()); + } + }; + } + } diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java index c84fef1e9..939f6241a 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionAdder.java @@ -17,7 +17,7 @@ import io.weaviate.client.v1.rbac.model.Permission; import lombok.AllArgsConstructor; -public class PermissionAdder extends AsyncBaseClient implements AsyncClientResult { +public class PermissionAdder extends AsyncBaseClient implements AsyncClientResult { private String role; private List> permissions = new ArrayList<>(); @@ -51,9 +51,9 @@ private static class Body { } @Override - public Future> run(FutureCallback> callback) { + public Future> run(FutureCallback> callback) { List permissions = WeaviatePermission.mergePermissions(this.permissions); - return sendPostRequest(path(), new Body(permissions), Void.class, callback); + return sendPostRequest(path(), new Body(permissions), callback, Result.voidToBooleanParser()); } private String path() { diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java index d0c3afc1f..ad7961f75 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionRemover.java @@ -17,7 +17,7 @@ import io.weaviate.client.v1.rbac.model.Permission; import lombok.AllArgsConstructor; -public class PermissionRemover extends AsyncBaseClient implements AsyncClientResult { +public class PermissionRemover extends AsyncBaseClient implements AsyncClientResult { private String role; private List> permissions = new ArrayList<>(); @@ -51,9 +51,9 @@ private static class Body { } @Override - public Future> run(FutureCallback> callback) { + public Future> run(FutureCallback> callback) { List permissions = WeaviatePermission.mergePermissions(this.permissions); - return sendPostRequest(path(), new Body(permissions), Void.class, callback); + return sendPostRequest(path(), new Body(permissions), callback, Result.voidToBooleanParser()); } private String path() { diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java index f0657945e..25bbb049f 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java @@ -15,7 +15,7 @@ import io.weaviate.client.v1.auth.provider.AccessTokenProvider; import lombok.AllArgsConstructor; -public class RoleAssigner extends AsyncBaseClient implements AsyncClientResult { +public class RoleAssigner extends AsyncBaseClient implements AsyncClientResult { private String user; private List roles = new ArrayList<>(); @@ -40,8 +40,8 @@ private static class Body { } @Override - public Future> run(FutureCallback> callback) { - return sendPostRequest(path(), new Body(this.roles), Void.class, callback); + public Future> run(FutureCallback> callback) { + return sendPostRequest(path(), new Body(this.roles), callback, Result.voidToBooleanParser()); } private String path() { diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java index b2393f49c..a952d9300 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleCreator.java @@ -16,7 +16,7 @@ import io.weaviate.client.v1.rbac.api.WeaviateRole; import io.weaviate.client.v1.rbac.model.Permission; -public class RoleCreator extends AsyncBaseClient implements AsyncClientResult { +public class RoleCreator extends AsyncBaseClient implements AsyncClientResult { private String name; private List> permissions = new ArrayList<>(); @@ -44,8 +44,8 @@ public RoleCreator withPermissions(Permission[]... permissions) { } @Override - public Future> run(FutureCallback> callback) { + public Future> run(FutureCallback> callback) { WeaviateRole role = new WeaviateRole(this.name, this.permissions); - return sendPostRequest("/authz/roles", role, Void.class, callback); + return sendPostRequest("/authz/roles", role, callback, Result.voidToBooleanParser()); } } diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java index e00e176bd..da495467f 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleDeleter.java @@ -11,7 +11,7 @@ import io.weaviate.client.base.Result; import io.weaviate.client.v1.auth.provider.AccessTokenProvider; -public class RoleDeleter extends AsyncBaseClient implements AsyncClientResult { +public class RoleDeleter extends AsyncBaseClient implements AsyncClientResult { private String name; public RoleDeleter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { @@ -24,7 +24,7 @@ public RoleDeleter withName(String name) { } @Override - public Future> run(FutureCallback> callback) { - return sendDeleteRequest("/authz/roles/" + this.name, null, Void.class, callback); + public Future> run(FutureCallback> callback) { + return sendDeleteRequest("/authz/roles/" + this.name, null, callback, Result.voidToBooleanParser()); } } diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java index bf21ad49c..ce06d2c2c 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java @@ -16,7 +16,7 @@ import io.weaviate.client.v1.auth.provider.AccessTokenProvider; import lombok.AllArgsConstructor; -public class RoleRevoker extends AsyncBaseClient implements AsyncClientResult { +public class RoleRevoker extends AsyncBaseClient implements AsyncClientResult { private String user; private List roles = new ArrayList<>(); @@ -41,8 +41,8 @@ private static class Body { } @Override - public Future> run(FutureCallback> callback) { - return sendPostRequest(path(), new Body(this.roles), Void.class, callback); + public Future> run(FutureCallback> callback) { + return sendPostRequest(path(), new Body(this.roles), callback, Result.voidToBooleanParser()); } private String path() { 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 index 0c86c3c84..55ea08949 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java @@ -7,12 +7,13 @@ 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.Permission; import lombok.AllArgsConstructor; -public class PermissionAdder extends BaseClient implements ClientResult { +public class PermissionAdder extends BaseClient implements ClientResult { private String role; private List> permissions = new ArrayList<>(); @@ -45,9 +46,9 @@ private static class Body { } @Override - public Result run() { + public Result run() { List permissions = WeaviatePermission.mergePermissions(this.permissions); - return new Result(sendPostRequest(path(), new Body(permissions), Void.class)); + return Result.voidToBoolean(sendPostRequest(path(), new Body(permissions), Void.class)); } private String path() { 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 index 25a88a9aa..623f1d7fd 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java @@ -12,7 +12,7 @@ import io.weaviate.client.v1.rbac.model.Permission; import lombok.AllArgsConstructor; -public class PermissionRemover extends BaseClient implements ClientResult { +public class PermissionRemover extends BaseClient implements ClientResult { private String role; private List> permissions = new ArrayList<>(); @@ -45,9 +45,9 @@ private static class Body { } @Override - public Result run() { + public Result run() { List permissions = WeaviatePermission.mergePermissions(this.permissions); - return new Result(sendPostRequest(path(), new Body(permissions), Void.class)); + return Result.voidToBoolean(sendPostRequest(path(), new Body(permissions), Void.class)); } private String path() { 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 index cc9ca3686..fd0bd3216 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java @@ -7,11 +7,12 @@ 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 lombok.AllArgsConstructor; -public class RoleAssigner extends BaseClient implements ClientResult { +public class RoleAssigner extends BaseClient implements ClientResult { private String user; private List roles = new ArrayList<>(); @@ -36,8 +37,10 @@ private static class Body { } @Override - public Result run() { - return new Result(sendPostRequest(path(), new Body(this.roles), Void.class)); + public Result run() { + Response resp = sendPostRequest(path(), new Body(this.roles), Void.class); + int status = resp.getStatusCode(); + return new Result(status, status == 200, resp.getErrors()); } private String path() { 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 index 149577aa8..01e6901ee 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java @@ -11,7 +11,7 @@ import io.weaviate.client.base.http.HttpClient; import io.weaviate.client.v1.rbac.model.Permission; -public class RoleCreator extends BaseClient implements ClientResult { +public class RoleCreator extends BaseClient implements ClientResult { private String name; private List> permissions = new ArrayList<>(); @@ -39,8 +39,8 @@ public RoleCreator withPermissions(Permission[]... permissions) { } @Override - public Result run() { + public Result run() { WeaviateRole role = new WeaviateRole(this.name, this.permissions); - return new Result(sendPostRequest("/authz/roles", role, Void.class)); + return Result.voidToBoolean(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 index 4a25b8847..579614b35 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java @@ -6,7 +6,7 @@ import io.weaviate.client.base.Result; import io.weaviate.client.base.http.HttpClient; -public class RoleDeleter extends BaseClient implements ClientResult { +public class RoleDeleter extends BaseClient implements ClientResult { private String name; public RoleDeleter(HttpClient httpClient, Config config) { @@ -19,7 +19,7 @@ public RoleDeleter withName(String name) { } @Override - public Result run() { - return new Result(sendDeleteRequest("/authz/roles/" + this.name, null, Void.class)); + public Result run() { + return Result.voidToBoolean(sendDeleteRequest("/authz/roles/" + this.name, null, Void.class)); } } 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 index d6f888c46..ea3487e6c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java @@ -12,7 +12,7 @@ import io.weaviate.client.base.http.HttpClient; import lombok.AllArgsConstructor; -public class RoleRevoker extends BaseClient implements ClientResult { +public class RoleRevoker extends BaseClient implements ClientResult { private String user; private List roles = new ArrayList<>(); @@ -37,8 +37,8 @@ private static class Body { } @Override - public Result run() { - return new Result(sendPostRequest(path(), new Body(this.roles), Void.class)); + public Result run() { + return Result.voidToBoolean(sendPostRequest(path(), new Body(this.roles), Void.class)); } private String path() { From 65fc863ea5675c2b48e6b1b3eb59c30c3a7ece7f Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 30 Jan 2025 19:21:00 +0100 Subject: [PATCH 22/40] chore: fix docstring --- src/main/java/io/weaviate/client/base/Result.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/weaviate/client/base/Result.java b/src/main/java/io/weaviate/client/base/Result.java index c3ebe00dc..a84e3cd66 100644 --- a/src/main/java/io/weaviate/client/base/Result.java +++ b/src/main/java/io/weaviate/client/base/Result.java @@ -61,7 +61,7 @@ public Result toErrorResult() { /** * Convert {@code Result} response to a {@code Result}. - * Returns true if response status code is 200. + * The result contains true if status code is 200. * * @param response Response from a call that does not return a value, like * {@link BaseClient#sendDeleteRequest}. @@ -74,10 +74,8 @@ public static Result voidToBoolean(Response response) { /** * Get a custom parser to convert {@code Result} response as to a - * {@code Result}. Result is true if response status code is 200. + * {@code Result}. The result contains true if status code is 200. * - * @param response Response from an async call that does not return a value, - * like {@link AsyncBaseClient#sendDeleteRequest}. * @return {@code Result} */ public static ResponseParser voidToBooleanParser() { From 205995a45275fe4f769d30b0c6f454e1016bd2ce Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 17 Feb 2025 16:45:37 +0100 Subject: [PATCH 23/40] feat: add changes from rc2 and align with Python's client - rename manage_users -> assign_and_revoke_users - add read_users action - add [crud]_roles actions - add scope for roles actions - default to VERBOSE verbosity for nodes permission if collection filter applied --- .../client/v1/rbac/model/NodesPermission.java | 12 ++- .../client/v1/rbac/model/Permission.java | 88 ++++++++++++++++++- .../client/v1/rbac/model/RolesPermission.java | 25 +++++- .../client/v1/rbac/model/UsersPermission.java | 10 +-- .../client/v1/rbac/model/PermissionTest.java | 22 +++-- .../tests/rbac/ClientRbacTestSuite.java | 2 +- 6 files changed, 136 insertions(+), 23 deletions(-) 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 index a3df6840a..ff88913ed 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -13,11 +13,18 @@ public class NodesPermission extends Permission { final String collection; final Verbosity verbosity; - /** NodesPermission for all collections. */ + /** Create permission scoped to all collections. */ public NodesPermission(Verbosity verbosity, Action action) { this("*", verbosity, action); } + /** + * Permission scoped to a collection with {@link Verbosity#VERBOSE}. + */ + public NodesPermission(String collection, Action action) { + this(collection, Verbosity.VERBOSE, action); + } + NodesPermission(Verbosity verbosity, String action) { this(verbosity, RbacAction.fromString(Action.class, action)); } @@ -26,8 +33,7 @@ public NodesPermission(Verbosity verbosity, Action action) { this(collection, verbosity, RbacAction.fromString(Action.class, action)); } - /** NodesPermission for a defined collection. */ - public NodesPermission(String collection, Verbosity verbosity, Action action) { + NodesPermission(String collection, Verbosity verbosity, Action action) { super(action); this.collection = collection; this.verbosity = verbosity; 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 index bfa1c2f52..6bc1278ec 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -1,6 +1,7 @@ package io.weaviate.client.v1.rbac.model; import io.weaviate.client.v1.rbac.api.WeaviatePermission; +import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -34,27 +35,53 @@ public static Permission fromWeaviate(WeaviatePermission perm) { } return new NodesPermission(nodes.getVerbosity(), action); } else if (perm.getRoles() != null) { - return new RolesPermission(perm.getRoles().getRole(), action); + RolesPermission roles = perm.getRoles(); + return new RolesPermission(roles.getRole(), roles.getScope(), action); } else if (perm.getTenants() != null) { return new TenantsPermission(action); } else if (RbacAction.isValid(ClusterPermission.Action.class, action)) { return new ClusterPermission(action); + } else if (RbacAction.isValid(UsersPermission.Action.class, action)) { + return new UsersPermission(action); } return null; } + /** + * Create {@link BackupsPermission} for a collection. + *

+ * Example: + * {@code Permission.backups(BackupsPermission.Action.MANAGE, "Pizza") } + */ public static BackupsPermission[] backups(BackupsPermission.Action action, String collection) { return new BackupsPermission[] { new BackupsPermission(collection, action) }; } + /** + * Create {@link ClusterPermission} permission. + *

+ * Example: {@code Permission.cluster(ClusterPermission.Action.READ, "Pizza") } + */ public static ClusterPermission[] cluster(ClusterPermission.Action action) { return new ClusterPermission[] { new ClusterPermission(action) }; } + /** + * Create permission for managing collection's configuration. + *

+ * Example: + * {@code Permission.collections("Pizza", CollectionsPermission.Action.READ) } + */ public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action action) { return new CollectionsPermission[] { new CollectionsPermission(collection, action) }; } + /** + * Create permission for collection's configuration. + *

+ * Example: + * {@code Permission.collections("Pizza", CollectionsPermission.Action.READ, CollectionsPermission.Action.UPDATE) } + */ public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action... actions) { CollectionsPermission[] permissions = new CollectionsPermission[actions.length]; for (int i = 0; i < actions.length; i++) { @@ -63,10 +90,23 @@ public static CollectionsPermission[] collections(String collection, Collections return permissions; } + /** + * Create permissions for multiple actions for managing collection's data. + *

+ * Example: + * {@code Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.UPDATE) } + */ public static DataPermission[] data(String collection, DataPermission.Action action) { return new DataPermission[] { new DataPermission(collection, action) }; } + /** + * Create permissions for multiple actions for managing collection's + * data. + *

+ * Example: + * {@code Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.UPDATE) } + */ public static DataPermission[] data(String collection, DataPermission.Action... actions) { DataPermission[] permissions = new DataPermission[actions.length]; for (int i = 0; i < actions.length; i++) { @@ -75,19 +115,43 @@ public static DataPermission[] data(String collection, DataPermission.Action... return permissions; } + /** + * Create {@link NodesPermission} scoped to all collections. + *

+ * Example: + * {@code Permission.nodes(NodesPermission.Verbosity.MINIMAL, NodesPermission.Action.READ) } + */ public static NodesPermission[] nodes(NodesPermission.Verbosity verbosity, NodesPermission.Action action) { return new NodesPermission[] { new NodesPermission(verbosity, action) }; } - public static NodesPermission[] nodes(String collection, NodesPermission.Verbosity verbosity, - NodesPermission.Action action) { - return new NodesPermission[] { new NodesPermission(collection, verbosity, action) }; + /** + * Create {@link NodesPermission} scoped to a specific collection. Verbosity is + * set to {@link Verbosity#VERBOSE} by default. + *

+ * Example: + * {@code Permission.nodes("Pizza", NodesPermission.Action.READ) } + */ + public static NodesPermission[] nodes(String collection, NodesPermission.Action action) { + return new NodesPermission[] { new NodesPermission(collection, action) }; } + /** + * Create {@link RolesPermission} for a role. + *

+ * Example: + * {@code Permission.roles("MyRole", RolesPermission.Action.READ) } + */ public static RolesPermission[] roles(String role, RolesPermission.Action action) { return new RolesPermission[] { new RolesPermission(role, action) }; } + /** + * Create {@link RolesPermission} for multiple actions. + *

+ * Example: + * {@code Permission.roles("MyRole", RolesPermission.Action.READ, RolesPermission.Action.UPDATE) } + */ public static RolesPermission[] roles(String role, RolesPermission.Action... actions) { RolesPermission[] permissions = new RolesPermission[actions.length]; for (int i = 0; i < actions.length; i++) { @@ -96,10 +160,26 @@ public static RolesPermission[] roles(String role, RolesPermission.Action... act return permissions; } + /** + * Create {@link TenantsPermission} for a tenant. + *

+ * Example: + * {@code Permission.tenants(TenantsPermission.Action.READ) } + */ public static TenantsPermission[] tenants(TenantsPermission.Action action) { return new TenantsPermission[] { new TenantsPermission(action) }; } + /** + * Create {@link UsersPermission}. + *

+ * Example: + * {@code Permission.users(UsersPermission.Action.READ) } + */ + public static UsersPermission[] users(UsersPermission.Action action) { + return new UsersPermission[] { new UsersPermission(action) }; + } + public String toString() { return String.format("Permission", this.action); } 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 index 877ac8789..e35e54666 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -1,5 +1,7 @@ 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; @@ -9,14 +11,20 @@ @EqualsAndHashCode(callSuper = true) public class RolesPermission extends Permission { final String role; + final Scope scope; public RolesPermission(String role, Action action) { + this(role, null, action); + } + + public RolesPermission(String role, Scope scope, Action action) { super(action); this.role = role; + this.scope = scope; } - RolesPermission(String role, String action) { - this(role, RbacAction.fromString(Action.class, action)); + RolesPermission(String role, Scope scope, String action) { + this(role, scope, RbacAction.fromString(Action.class, action)); } @Override @@ -26,10 +34,23 @@ public WeaviatePermission toWeaviate() { @AllArgsConstructor public enum Action implements RbacAction { + CREATE("create_roles"), READ("read_roles"), + UPDATE("update_roles"), + DELETE("delete_roles"), + + /* Backward compatibility with 1.28. */ + @Deprecated MANAGE("manage_roles"); @Getter private final String value; } + + public enum Scope { + @SerializedName("all") + ALL, + @SerializedName("match") + MATCH; + } } 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 index 4639b1779..8f288fc9c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -5,12 +5,7 @@ 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 class UsersPermission extends Permission { public UsersPermission(Action action) { super(action); } @@ -26,7 +21,8 @@ public WeaviatePermission toWeaviate() { @AllArgsConstructor public enum Action implements RbacAction { - MANAGE("manage_users"); + READ("read_users"), + ASSIGN_AND_REVOKE("assign_and_revoke_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 index 4ba966300..2ed2c433d 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -17,18 +17,18 @@ 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() { BackupsPermission backups = new BackupsPermission("Pizza", BackupsPermission.Action.MANAGE); DataPermission data = new DataPermission("Pizza", DataPermission.Action.MANAGE); - NodesPermission nodes = new NodesPermission("Pizza", Verbosity.MINIMAL, NodesPermission.Action.READ); + NodesPermission nodes = new NodesPermission("Pizza", NodesPermission.Action.READ); RolesPermission roles = new RolesPermission("TestWriter", RolesPermission.Action.MANAGE); CollectionsPermission collections = new CollectionsPermission("Pizza", CollectionsPermission.Action.CREATE); ClusterPermission cluster = new ClusterPermission(ClusterPermission.Action.READ); TenantsPermission tenants = new TenantsPermission(TenantsPermission.Action.READ); + UsersPermission users = new UsersPermission(UsersPermission.Action.READ); return new Object[][] { { @@ -66,6 +66,11 @@ public static Object[][] serializationTestCases() { (Supplier>) () -> tenants, new WeaviatePermission("read_tenants", tenants), }, + { + "users permission", + (Supplier>) () -> users, + new WeaviatePermission("read_users"), + }, }; } @@ -110,12 +115,18 @@ public void testDefaultTenantsPermission() { .returns("*", TenantsPermission::getTenant); } + @Test + public void testDefaultRolesPermission() { + RolesPermission perm = new RolesPermission("ExampleRole", RolesPermission.Action.READ); + assertThat(perm).as("tenants permission must have scope=null") + .returns(null, RolesPermission::getScope); + } + @DataMethod(source = PermissionTest.class, method = "serializationTestCases") @Test public void testFromWeaviate(String name, Supplier> expectedFunc, WeaviatePermission input) throws Exception { - System.out.println("fromWeaviate: " + name); Permission expected = expectedFunc.get(); Permission actual = Permission.fromWeaviate(input); MatcherAssert.assertThat(name, actual, sameAs(expected)); @@ -155,10 +166,10 @@ public static Object[][] groupedConstructors() { { Permission.roles("TestRole", RolesPermission.Action.READ, - RolesPermission.Action.MANAGE), + RolesPermission.Action.UPDATE), new String[] { "read_roles", - "manage_roles", + "update_roles", }, }, }; @@ -167,7 +178,6 @@ public static Object[][] groupedConstructors() { @DataMethod(source = PermissionTest.class, method = "groupedConstructors") @Test public void testGroupedConstructors(Permission>[] permissions, String[] expectedActions) { - Arrays.stream(permissions).map(Permission::getAction).forEach(a -> System.out.println(a)); Object[] actualActions = Arrays.stream(permissions).map(Permission::getAction).toArray(); assertArrayEquals(expectedActions, actualActions, "set of allowed actions do not match"); } diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index a2e423528..3a9cc1c14 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -130,7 +130,7 @@ public void testCreate(Supplier rbac) { Permission[][] wantPermissions = new Permission[][] { Permission.backups(BackupsPermission.Action.MANAGE, myCollection), Permission.cluster(ClusterPermission.Action.READ), - Permission.nodes(myCollection, Verbosity.MINIMAL, NodesPermission.Action.READ), + Permission.nodes(myCollection, NodesPermission.Action.READ), Permission.roles(viewerRole, RolesPermission.Action.MANAGE), Permission.collections(myCollection, CollectionsPermission.Action.CREATE), Permission.data(myCollection, DataPermission.Action.UPDATE), From 4904cea8b3066fafd7bc5ecbc1b15cb388bd0ec1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 17 Feb 2025 18:00:47 +0100 Subject: [PATCH 24/40] refactor: read/manage user roles from 'users' namespace Separate RBAC- and User-related tests Upgrage Weaviate server version to v1.28.6 --- .../io/weaviate/client/WeaviateClient.java | 5 + .../client/v1/async/WeaviateAsyncClient.java | 5 + .../weaviate/client/v1/async/rbac/Roles.java | 16 --- .../weaviate/client/v1/async/users/Users.java | 31 ++++ .../{rbac => users}/api/RoleAssigner.java | 2 +- .../{rbac => users}/api/RoleRevoker.java | 2 +- .../{rbac => users}/api/UserRolesGetter.java | 5 +- .../io/weaviate/client/v1/rbac/Roles.java | 18 --- .../io/weaviate/client/v1/users/Users.java | 30 ++++ .../v1/{rbac => users}/api/RoleAssigner.java | 2 +- .../v1/{rbac => users}/api/RoleRevoker.java | 2 +- .../{rbac => users}/api/UserRolesGetter.java | 6 +- .../integration/client/WeaviateVersion.java | 6 +- .../client/async/rbac/ClientRbacTest.java | 22 +-- .../client/async/users/ClientUsersTest.java | 45 ++++++ .../client/rbac/ClientRbacTest.java | 20 --- .../client/users/ClientUsersTest.java | 40 ++++++ .../tests/rbac/ClientRbacTestSuite.java | 55 -------- .../tests/users/ClientUsersTestSuite.java | 132 ++++++++++++++++++ 19 files changed, 301 insertions(+), 143 deletions(-) create mode 100644 src/main/java/io/weaviate/client/v1/async/users/Users.java rename src/main/java/io/weaviate/client/v1/async/{rbac => users}/api/RoleAssigner.java (96%) rename src/main/java/io/weaviate/client/v1/async/{rbac => users}/api/RoleRevoker.java (97%) rename src/main/java/io/weaviate/client/v1/async/{rbac => users}/api/UserRolesGetter.java (90%) create mode 100644 src/main/java/io/weaviate/client/v1/users/Users.java rename src/main/java/io/weaviate/client/v1/{rbac => users}/api/RoleAssigner.java (96%) rename src/main/java/io/weaviate/client/v1/{rbac => users}/api/RoleRevoker.java (96%) rename src/main/java/io/weaviate/client/v1/{rbac => users}/api/UserRolesGetter.java (85%) create mode 100644 src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java create mode 100644 src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java create mode 100644 src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java diff --git a/src/main/java/io/weaviate/client/WeaviateClient.java b/src/main/java/io/weaviate/client/WeaviateClient.java index adf88b0d2..dcd9f3fed 100644 --- a/src/main/java/io/weaviate/client/WeaviateClient.java +++ b/src/main/java/io/weaviate/client/WeaviateClient.java @@ -22,6 +22,7 @@ import io.weaviate.client.v1.misc.api.MetaGetter; import io.weaviate.client.v1.rbac.Roles; import io.weaviate.client.v1.schema.Schema; +import io.weaviate.client.v1.users.Users; public class WeaviateClient { private final Config config; @@ -106,6 +107,10 @@ public Roles roles() { return new Roles(httpClient, config); } + public Users users() { + return new Users(httpClient, config); + } + private DbVersionProvider initDbVersionProvider() { MetaGetter metaGetter = new Misc(httpClient, config, null).metaGetter(); DbVersionProvider.VersionGetter getter = () -> Optional.ofNullable(metaGetter.run()) diff --git a/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java b/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java index e01573607..c19826587 100644 --- a/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java +++ b/src/main/java/io/weaviate/client/v1/async/WeaviateAsyncClient.java @@ -21,6 +21,7 @@ import io.weaviate.client.v1.async.misc.Misc; import io.weaviate.client.v1.async.rbac.Roles; import io.weaviate.client.v1.async.schema.Schema; +import io.weaviate.client.v1.async.users.Users; import io.weaviate.client.v1.auth.provider.AccessTokenProvider; import io.weaviate.client.v1.misc.model.Meta; @@ -79,6 +80,10 @@ public Roles roles() { return new Roles(client, config, tokenProvider); } + public Users users() { + return new Users(client, config, tokenProvider); + } + private DbVersionProvider initDbVersionProvider() { DbVersionProvider.VersionGetter getter = () -> Optional.ofNullable(this.getMeta()) .filter(result -> !result.hasErrors()) diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java b/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java index 5ed14ac27..13c132e15 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/Roles.java @@ -8,13 +8,10 @@ import io.weaviate.client.v1.async.rbac.api.PermissionChecker; import io.weaviate.client.v1.async.rbac.api.PermissionRemover; import io.weaviate.client.v1.async.rbac.api.RoleAllGetter; -import io.weaviate.client.v1.async.rbac.api.RoleAssigner; import io.weaviate.client.v1.async.rbac.api.RoleCreator; import io.weaviate.client.v1.async.rbac.api.RoleDeleter; import io.weaviate.client.v1.async.rbac.api.RoleExists; import io.weaviate.client.v1.async.rbac.api.RoleGetter; -import io.weaviate.client.v1.async.rbac.api.RoleRevoker; -import io.weaviate.client.v1.async.rbac.api.UserRolesGetter; import io.weaviate.client.v1.auth.provider.AccessTokenProvider; import lombok.RequiredArgsConstructor; @@ -68,11 +65,6 @@ public RoleGetter getter() { return new RoleGetter(client, config, tokenProvider); }; - /** Get roles assigned to a user. */ - public UserRolesGetter userRolesGetter() { - return new UserRolesGetter(client, config, tokenProvider); - }; - /** Get users assigned to a role. */ public AssignedUsersGetter assignedUsersGetter() { return new AssignedUsersGetter(client, config, tokenProvider); @@ -82,12 +74,4 @@ public AssignedUsersGetter assignedUsersGetter() { public RoleExists exists() { return new RoleExists(client, config, tokenProvider); } - - public RoleAssigner assigner() { - return new RoleAssigner(client, config, tokenProvider); - } - - public RoleRevoker revoker() { - return new RoleRevoker(client, config, tokenProvider); - } } diff --git a/src/main/java/io/weaviate/client/v1/async/users/Users.java b/src/main/java/io/weaviate/client/v1/async/users/Users.java new file mode 100644 index 000000000..c32cf6d2d --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/users/Users.java @@ -0,0 +1,31 @@ +package io.weaviate.client.v1.async.users; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; + +import io.weaviate.client.Config; +import io.weaviate.client.v1.async.users.api.RoleAssigner; +import io.weaviate.client.v1.async.users.api.RoleRevoker; +import io.weaviate.client.v1.async.users.api.UserRolesGetter; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Users { + + private final CloseableHttpAsyncClient client; + private final Config config; + private final AccessTokenProvider tokenProvider; + + /** Get roles assigned to a user. */ + public UserRolesGetter userRolesGetter() { + return new UserRolesGetter(client, config, tokenProvider); + }; + + public RoleAssigner assigner() { + return new RoleAssigner(client, config, tokenProvider); + } + + public RoleRevoker revoker() { + return new RoleRevoker(client, config, tokenProvider); + } +} diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java similarity index 96% rename from src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java rename to src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java index 25bbb049f..885844a6b 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.async.rbac.api; +package io.weaviate.client.v1.async.users.api; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java similarity index 97% rename from src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java rename to src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java index ce06d2c2c..6943e1443 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.async.rbac.api; +package io.weaviate.client.v1.async.users.api; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java similarity index 90% rename from src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java rename to src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java index c405e886a..92aec8f47 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.async.rbac.api; +package io.weaviate.client.v1.async.users.api; import java.util.ArrayList; import java.util.Arrays; @@ -36,8 +36,7 @@ public UserRolesGetter withUser(String user) { @Override public Future>> run(FutureCallback>> callback) { - String path = this.user == null ? "/authz/users/own-roles" : this.path(); - return sendGetRequest(path, callback, new ResponseParser>() { + return sendGetRequest(path(), callback, new ResponseParser>() { @Override public Result> parse(HttpResponse response, String body, ContentType contentType) { Response resp = this.serializer.toResponse(response.getCode(), body, WeaviateRole[].class); diff --git a/src/main/java/io/weaviate/client/v1/rbac/Roles.java b/src/main/java/io/weaviate/client/v1/rbac/Roles.java index c1c0c63e7..c1bbbe35e 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/Roles.java +++ b/src/main/java/io/weaviate/client/v1/rbac/Roles.java @@ -7,13 +7,10 @@ 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 @@ -66,11 +63,6 @@ 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); @@ -80,14 +72,4 @@ public AssignedUsersGetter assignedUsersGetter() { 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/users/Users.java b/src/main/java/io/weaviate/client/v1/users/Users.java new file mode 100644 index 000000000..2db251d36 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/users/Users.java @@ -0,0 +1,30 @@ +package io.weaviate.client.v1.users; + +import io.weaviate.client.Config; +import io.weaviate.client.base.http.HttpClient; +import io.weaviate.client.v1.users.api.RoleAssigner; +import io.weaviate.client.v1.users.api.RoleRevoker; +import io.weaviate.client.v1.users.api.UserRolesGetter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Users { + + private final HttpClient httpClient; + private final Config config; + + /** Get roles assigned to a user. */ + public UserRolesGetter userRolesGetter() { + return new UserRolesGetter(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/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java similarity index 96% rename from src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java rename to src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java index fd0bd3216..2a884a605 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.rbac.api; +package io.weaviate.client.v1.users.api; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java similarity index 96% rename from src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java rename to src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java index ea3487e6c..bbb2d888b 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.rbac.api; +package io.weaviate.client.v1.users.api; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java similarity index 85% rename from src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java rename to src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java index 87eaedb6c..2d42c3591 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java @@ -1,4 +1,4 @@ -package io.weaviate.client.v1.rbac.api; +package io.weaviate.client.v1.users.api; import java.util.ArrayList; import java.util.Arrays; @@ -11,6 +11,7 @@ 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.api.WeaviateRole; import io.weaviate.client.v1.rbac.model.Role; public class UserRolesGetter extends BaseClient implements ClientResult> { @@ -28,8 +29,7 @@ public UserRolesGetter withUser(String user) { @Override public Result> run() { - String path = this.user == null ? "/authz/users/own-roles" : this.path(); - Response resp = sendGetRequest(path, WeaviateRole[].class); + Response resp = sendGetRequest(path(), WeaviateRole[].class); List roles = Optional.ofNullable(resp.getBody()) .map(Arrays::asList) .orElse(new ArrayList<>()) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index 75ef83870..eb5d37bca 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.28.3-f73fcee"; + public static final String WEAVIATE_IMAGE = "1.28.6-660c1fa"; // to be set according to weaviate docker image - public static final String EXPECTED_WEAVIATE_VERSION = "1.28.3"; + public static final String EXPECTED_WEAVIATE_VERSION = "1.28.6"; // to be set according to weaviate docker image - public static final String EXPECTED_WEAVIATE_GIT_HASH = "f73fcee"; + public static final String EXPECTED_WEAVIATE_GIT_HASH = "660c1fa"; private WeaviateVersion() { } diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java index 6233fdb7b..c63f8a52f 100644 --- a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -33,7 +33,7 @@ public ClientRbacTest(Config config, String apiKey) { * methods without clashing with {@link ClientRbacTestSuite.Rbac} method * signatures. */ - private T rethrow(Callable c) { + protected T rethrow(Callable c) { try { return c.call(); } catch (Exception e) { @@ -51,16 +51,6 @@ public Result> getAll() { return rethrow(() -> roles.allGetter().run().get()); } - @Override - public Result> getUserRoles() { - return rethrow(() -> roles.userRolesGetter().run().get()); - } - - @Override - public Result> getUserRoles(String user) { - return rethrow(() -> roles.userRolesGetter().withUser(user).run().get()); - } - @Override public Result> getAssignedUsers(String role) { return rethrow(() -> roles.assignedUsersGetter().withRole(role).run().get()); @@ -110,14 +100,4 @@ public Result removePermissions(String role, Permission... permissions) { public Result removePermissions(String role, Permission[]... permissions) { return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); } - - @Override - public Result assignRoles(String user, String... roles) { - return rethrow(() -> this.roles.assigner().withUser(user).witRoles(roles).run().get()); - } - - @Override - public Result revokeRoles(String user, String... roles) { - return rethrow(() -> this.roles.revoker().withUser(user).witRoles(roles).run().get()); - } } diff --git a/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java new file mode 100644 index 000000000..095001dce --- /dev/null +++ b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java @@ -0,0 +1,45 @@ +package io.weaviate.integration.client.async.users; + +import java.util.List; + +import io.weaviate.client.Config; +import io.weaviate.client.WeaviateAuthClient; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.async.users.Users; +import io.weaviate.client.v1.auth.exception.AuthException; +import io.weaviate.client.v1.rbac.model.Role; +import io.weaviate.integration.client.async.rbac.ClientRbacTest; +import io.weaviate.integration.tests.users.ClientUsersTestSuite; + +/** + * ClientUsersTest is a {@link ClientUsersTestSuite.Users} implementation and a + * wrapper around WeaviateAsyncClient.Roles client which allows the latter to be + * used in the ClientUsersTestSuite. + */ +public class ClientUsersTest extends ClientRbacTest implements ClientUsersTestSuite.Users { + private Users users; + + public ClientUsersTest(Config config, String apiKey) { + super(config, apiKey); + try { + this.users = WeaviateAuthClient.apiKey(config, apiKey).async().users(); + } catch (AuthException e) { + throw new RuntimeException(e); + } + } + + @Override + public Result> getUserRoles(String user) { + return rethrow(() -> users.userRolesGetter().withUser(user).run().get()); + } + + @Override + public Result assignRoles(String user, String... roles) { + return rethrow(() -> this.users.assigner().withUser(user).witRoles(roles).run().get()); + } + + @Override + public Result revokeRoles(String user, String... roles) { + return rethrow(() -> this.users.revoker().withUser(user).witRoles(roles).run().get()); + } +} diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index fc0c99c0d..6540c47e5 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -32,16 +32,6 @@ public Result> getAll() { return roles.allGetter().run(); } - @Override - public Result> getUserRoles() { - return roles.userRolesGetter().run(); - } - - @Override - public Result> getUserRoles(String user) { - return roles.userRolesGetter().withUser(user).run(); - } - @Override public Result> getAssignedUsers(String role) { return roles.assignedUsersGetter().withRole(role).run(); @@ -91,14 +81,4 @@ public Result removePermissions(String role, Permission... permissions) { public Result removePermissions(String role, Permission[]... permissions) { return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); } - - @Override - public Result assignRoles(String user, String... roles) { - return this.roles.assigner().withUser(user).witRoles(roles).run(); - } - - @Override - public Result revokeRoles(String user, String... roles) { - return this.roles.revoker().withUser(user).witRoles(roles).run(); - } } diff --git a/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java new file mode 100644 index 000000000..5dd61694b --- /dev/null +++ b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java @@ -0,0 +1,40 @@ +package io.weaviate.integration.client.users; + +import java.util.List; + +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.model.Role; +import io.weaviate.client.v1.users.Users; +import io.weaviate.integration.client.rbac.ClientRbacTest; +import io.weaviate.integration.tests.users.ClientUsersTestSuite; + +public class ClientUsersTest extends ClientRbacTest implements ClientUsersTestSuite.Users { + private Users users; + + public ClientUsersTest(Config config, String apiKey) { + super(config, apiKey); + try { + this.users = WeaviateAuthClient.apiKey(config, apiKey).users(); + } catch (AuthException e) { + throw new RuntimeException(e); + } + } + + @Override + public Result> getUserRoles(String user) { + return users.userRolesGetter().withUser(user).run(); + } + + @Override + public Result assignRoles(String user, String... roles) { + return this.users.assigner().withUser(user).witRoles(roles).run(); + } + + @Override + public Result revokeRoles(String user, String... roles) { + return this.users.revoker().withUser(user).witRoles(roles).run(); + } +} diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 3a9cc1c14..7a1ac47ae 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -5,7 +5,6 @@ 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.assumeTrue; import java.util.List; import java.util.function.Supplier; @@ -13,7 +12,6 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; -import org.junit.jupiter.api.Assertions; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.testcontainers.weaviate.WeaviateContainer; @@ -28,7 +26,6 @@ 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; @@ -84,25 +81,6 @@ public void testGetAll(Supplier rbac) { assertThat(all.get(1)).returns(viewerRole, Role::getName); } - /** - * Roles retrieved for "current user" should be identical to the ones - * retrieved for them explicitly (by passing the username). - */ - @DataMethod(source = ClientRbacTestSuite.class, method = "clients") - @Test - public void testGetUserRoles(Supplier rbac) { - Rbac roles = rbac.get(); - Result> responseCurrent = roles.getUserRoles(); - assertThat(responseCurrent.getError()).as("get roles for current user error").isNull(); - Result> responseAdminUser = roles.getUserRoles(adminUser); - 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"); - } - /** Admin user should have the admin role assigned to them. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") @Test @@ -281,31 +259,6 @@ public void testRemovePermissionsMultipleAction(Supplier rbac) { } } - /** User can be assigned a role and the role can be revoked. */ - @DataMethod(source = ClientRbacTestSuite.class, method = "clients") - @Test - public void testAssignRevokeRole(Supplier rbac) { - Rbac roles = rbac.get(); - String myRole = roleName("VectorOwner"); - try { - // Arrange - roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); - - // Act: Assign - roles.assignRoles(adminUser, myRole); - assumeTrue(checkHasRole(roles, adminUser, myRole), adminUser + " should have the assigned role"); - - // Act: Revoke - Result response = roles.revokeRoles(adminUser, myRole); - assertNull("revoke operation error", response.getError()); - - // Assert - assertFalse("should not have " + myRole + " role", checkHasRole(roles, adminUser, myRole)); - } finally { - roles.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); @@ -329,10 +282,6 @@ public interface Rbac { Result> getAll(); - Result> getUserRoles(); - - Result> getUserRoles(String user); - Result> getAssignedUsers(String role); void createRole(String role, Permission... permissions); @@ -352,10 +301,6 @@ public interface Rbac { Result removePermissions(String role, Permission... permissions); Result removePermissions(String role, Permission[]... permissions); - - Result assignRoles(String user, String... roles); - - Result revokeRoles(String user, String... roles); } } diff --git a/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java new file mode 100644 index 000000000..e83036619 --- /dev/null +++ b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java @@ -0,0 +1,132 @@ +package io.weaviate.integration.tests.users; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.util.List; +import java.util.function.Supplier; + +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; +import org.testcontainers.weaviate.WeaviateContainer; + +import com.jparams.junit4.JParamsTestRunner; +import com.jparams.junit4.data.DataMethod; + +import io.weaviate.client.Config; +import io.weaviate.client.base.Result; +import io.weaviate.client.v1.rbac.model.Permission; +import io.weaviate.client.v1.rbac.model.Role; +import io.weaviate.client.v1.rbac.model.TenantsPermission; +import io.weaviate.integration.client.WeaviateDockerImage; +import io.weaviate.integration.client.WeaviateWithRbacContainer; +import io.weaviate.integration.tests.rbac.ClientRbacTestSuite; + +@RunWith(JParamsTestRunner.class) +public class ClientUsersTestSuite { + + private static final String adminUser = "john-doe"; + private static final String API_KEY = WeaviateWithRbacContainer.makeSecret(adminUser); + + @Rule + public TestName currentTest = new TestName(); + + @ClassRule + public static WeaviateContainer weaviate = new WeaviateWithRbacContainer( + WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE, + adminUser); + + public static Config config() { + return new Config("http", weaviate.getHttpHostAddress()); + } + + public static Object[][] clients() { + try { + return new Object[][] { + { (Supplier) () -> new io.weaviate.integration.client.users.ClientUsersTest(config(), API_KEY) }, + { (Supplier) () -> new io.weaviate.integration.client.async.users.ClientUsersTest(config(), API_KEY) } + }; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Roles retrieved for "current user" should be identical to the ones + * retrieved for them explicitly (by passing the username). + */ + @Ignore // wip + @DataMethod(source = ClientUsersTestSuite.class, method = "clients") + @Test + public void testGetUserRoles(Supplier rbac) { + Users roles = rbac.get(); + // Result> responseCurrent = roles.getUserRoles(); + // assertThat(responseCurrent.getError()).as("get roles for current user + // error").isNull(); + // Result> responseAdminUser = roles.getUserRoles(adminUser); + // 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"); + } + + /** User can be assigned a role and the role can be revoked. */ + @DataMethod(source = ClientUsersTestSuite.class, method = "clients") + @Test + public void testAssignRevokeRole(Supplier rbac) { + Users roles = rbac.get(); + String myRole = roleName("VectorOwner"); + try { + // Arrange + roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); + + // Act: Assign + roles.assignRoles(adminUser, myRole); + assumeTrue(checkHasRole(roles, adminUser, myRole), adminUser + " should have the assigned role"); + + // Act: Revoke + Result response = roles.revokeRoles(adminUser, myRole); + assertNull("revoke operation error", response.getError()); + + // Assert + assertFalse("should not have " + myRole + " role", checkHasRole(roles, adminUser, myRole)); + } finally { + roles.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 checkHasRole(Users roles, String user, String role) { + return roles.getAssignedUsers(role).getResult().contains(user); + } + + /** + * Sync and async test suits should provide an implementation of this interface. + * This way the test suite can be written once with very little + * boilerplate/overhead. + * + * Extends {@link ClientRbacTestSuite.Rbac} because many tests require the + * functionality for creating / deleting / verifying roles. + */ + public interface Users extends ClientRbacTestSuite.Rbac { + Result> getUserRoles(String user); + + Result assignRoles(String user, String... roles); + + Result revokeRoles(String user, String... roles); + } + +} From cdac8b789950964bd9f54285696147ac8942d4d0 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 17 Feb 2025 18:50:53 +0100 Subject: [PATCH 25/40] feat: fetch current user via MyUserGetter (/users/own-info) --- .../weaviate/client/v1/async/users/Users.java | 6 +++ .../v1/async/users/api/MyUserGetter.java | 39 ++++++++++++++++++ .../io/weaviate/client/v1/users/Users.java | 6 +++ .../client/v1/users/api/MyUserGetter.java | 24 +++++++++++ .../client/v1/users/api/WeaviateUser.java | 16 ++++++++ .../weaviate/client/v1/users/model/User.java | 21 ++++++++++ .../client/async/rbac/ClientRbacTest.java | 37 +++++++++-------- .../client/async/users/ClientUsersTest.java | 12 ++++-- .../client/users/ClientUsersTest.java | 6 +++ .../tests/users/ClientUsersTestSuite.java | 41 +++++++++---------- 10 files changed, 167 insertions(+), 41 deletions(-) create mode 100644 src/main/java/io/weaviate/client/v1/async/users/api/MyUserGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/users/api/MyUserGetter.java create mode 100644 src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java create mode 100644 src/main/java/io/weaviate/client/v1/users/model/User.java diff --git a/src/main/java/io/weaviate/client/v1/async/users/Users.java b/src/main/java/io/weaviate/client/v1/async/users/Users.java index c32cf6d2d..f77bdd897 100644 --- a/src/main/java/io/weaviate/client/v1/async/users/Users.java +++ b/src/main/java/io/weaviate/client/v1/async/users/Users.java @@ -3,6 +3,7 @@ import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import io.weaviate.client.Config; +import io.weaviate.client.v1.async.users.api.MyUserGetter; import io.weaviate.client.v1.async.users.api.RoleAssigner; import io.weaviate.client.v1.async.users.api.RoleRevoker; import io.weaviate.client.v1.async.users.api.UserRolesGetter; @@ -16,6 +17,11 @@ public class Users { private final Config config; private final AccessTokenProvider tokenProvider; + /** Get information about the current user. */ + public MyUserGetter myUserGetter() { + return new MyUserGetter(client, config, tokenProvider); + }; + /** Get roles assigned to a user. */ public UserRolesGetter userRolesGetter() { return new UserRolesGetter(client, config, tokenProvider); diff --git a/src/main/java/io/weaviate/client/v1/async/users/api/MyUserGetter.java b/src/main/java/io/weaviate/client/v1/async/users/api/MyUserGetter.java new file mode 100644 index 000000000..0c4a840e3 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/async/users/api/MyUserGetter.java @@ -0,0 +1,39 @@ +package io.weaviate.client.v1.async.users.api; + +import java.util.Optional; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpResponse; + +import io.weaviate.client.Config; +import io.weaviate.client.base.AsyncBaseClient; +import io.weaviate.client.base.AsyncClientResult; +import io.weaviate.client.base.Response; +import io.weaviate.client.base.Result; +import io.weaviate.client.base.http.async.ResponseParser; +import io.weaviate.client.v1.auth.provider.AccessTokenProvider; +import io.weaviate.client.v1.users.api.WeaviateUser; +import io.weaviate.client.v1.users.model.User; + +public class MyUserGetter extends AsyncBaseClient implements AsyncClientResult { + public MyUserGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { + super(httpClient, config, tokenProvider); + } + + @Override + public Future> run(FutureCallback> callback) { + return sendGetRequest("/users/own-info", callback, new ResponseParser() { + @Override + public Result parse(HttpResponse response, String body, ContentType contentType) { + Response resp = this.serializer.toResponse(response.getCode(), body, WeaviateUser.class); + User user = Optional.ofNullable(resp.getBody()) + .map(WeaviateUser::toUser) + .orElse(null); + return new Result<>(resp.getStatusCode(), user, resp.getErrors()); + } + }); + } +} diff --git a/src/main/java/io/weaviate/client/v1/users/Users.java b/src/main/java/io/weaviate/client/v1/users/Users.java index 2db251d36..8bd84d787 100644 --- a/src/main/java/io/weaviate/client/v1/users/Users.java +++ b/src/main/java/io/weaviate/client/v1/users/Users.java @@ -2,6 +2,7 @@ import io.weaviate.client.Config; import io.weaviate.client.base.http.HttpClient; +import io.weaviate.client.v1.users.api.MyUserGetter; import io.weaviate.client.v1.users.api.RoleAssigner; import io.weaviate.client.v1.users.api.RoleRevoker; import io.weaviate.client.v1.users.api.UserRolesGetter; @@ -13,6 +14,11 @@ public class Users { private final HttpClient httpClient; private final Config config; + /** Get information about the current user. */ + public MyUserGetter myUserGetter() { + return new MyUserGetter(httpClient, config); + }; + /** Get roles assigned to a user. */ public UserRolesGetter userRolesGetter() { return new UserRolesGetter(httpClient, config); diff --git a/src/main/java/io/weaviate/client/v1/users/api/MyUserGetter.java b/src/main/java/io/weaviate/client/v1/users/api/MyUserGetter.java new file mode 100644 index 000000000..88bcf7222 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/users/api/MyUserGetter.java @@ -0,0 +1,24 @@ +package io.weaviate.client.v1.users.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.users.model.User; + +public class MyUserGetter extends BaseClient implements ClientResult { + public MyUserGetter(HttpClient httpClient, Config config) { + super(httpClient, config); + } + + @Override + public Result run() { + Response resp = sendGetRequest("/users/own-info", WeaviateUser.class); + User user = Optional.ofNullable(resp.getBody()).map(WeaviateUser::toUser).orElse(null); + return new Result<>(resp.getStatusCode(), user, resp.getErrors()); + } +} diff --git a/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java new file mode 100644 index 000000000..c1e2ede99 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java @@ -0,0 +1,16 @@ +package io.weaviate.client.v1.users.api; + +import java.util.List; + +import io.weaviate.client.v1.rbac.api.WeaviateRole; +import io.weaviate.client.v1.users.model.User; + +public class WeaviateUser { + String name; + String id; + List roles; + + public User toUser() { + return new User(name, id, roles.stream().map(WeaviateRole::toRole).toList()); + } +} diff --git a/src/main/java/io/weaviate/client/v1/users/model/User.java b/src/main/java/io/weaviate/client/v1/users/model/User.java new file mode 100644 index 000000000..52023e6a0 --- /dev/null +++ b/src/main/java/io/weaviate/client/v1/users/model/User.java @@ -0,0 +1,21 @@ +package io.weaviate.client.v1.users.model; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import io.weaviate.client.v1.rbac.model.Role; +import lombok.Getter; + +@Getter +public class User { + String name; + String userId; + Map roles; + + public User(String name, String id, List roles) { + this.name = name; + this.userId = id; + this.roles = roles.stream().collect(Collectors.toMap(Role::getName, r -> r)); + } +} diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java index c63f8a52f..0a74f2f9b 100644 --- a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.Future; import io.weaviate.client.Config; import io.weaviate.client.WeaviateAuthClient; @@ -11,6 +12,7 @@ import io.weaviate.client.v1.rbac.model.Permission; import io.weaviate.client.v1.rbac.model.Role; import io.weaviate.integration.tests.rbac.ClientRbacTestSuite; +import io.weaviate.integration.tests.users.ClientUsersTestSuite; /** * ClientRbacTest is a {@link ClientRbacTestSuite.Rbac} implementation and a @@ -29,13 +31,14 @@ public ClientRbacTest(Config config, String apiKey) { } /** - * Rethrow any exception as a RuntimeException to allow calling AsyncClient - * methods without clashing with {@link ClientRbacTestSuite.Rbac} method - * signatures. + * Get Future result and rethrow any exception as a RuntimeException + * to allow calling AsyncClient methods without clashing with + * {@link ClientRbacTestSuite.Rbac} and {@link ClientUsersTestSuite.Users} + * method signatures. */ - protected T rethrow(Callable c) { + protected T rethrow(Callable> c) { try { - return c.call(); + return c.call().get(); } catch (Exception e) { throw new RuntimeException(e); } @@ -43,61 +46,61 @@ protected T rethrow(Callable c) { @Override public Result getRole(String role) { - return rethrow(() -> roles.getter().withName(role).run().get()); + return rethrow(() -> roles.getter().withName(role).run()); } @Override public Result> getAll() { - return rethrow(() -> roles.allGetter().run().get()); + return rethrow(() -> roles.allGetter().run()); } @Override public Result> getAssignedUsers(String role) { - return rethrow(() -> roles.assignedUsersGetter().withRole(role).run().get()); + return rethrow(() -> roles.assignedUsersGetter().withRole(role).run()); } @Override public void createRole(String role, Permission... permissions) { - rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run().get()); + rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); } @Override public void createRole(String role, Permission[]... permissions) { - rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run().get()); + rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); } @Override public void deleteRole(String role) { - rethrow(() -> roles.deleter().withName(role).run().get()); + rethrow(() -> roles.deleter().withName(role).run()); } @Override public Result hasPermission(String role, Permission perm) { - return rethrow(() -> roles.permissionChecker().withRole(role).withPermission(perm).run().get()); + return rethrow(() -> roles.permissionChecker().withRole(role).withPermission(perm).run()); } @Override public Result exists(String role) { - return rethrow(() -> roles.exists().withName(role).run().get()); + return rethrow(() -> roles.exists().withName(role).run()); } @Override public Result addPermissions(String role, Permission... permissions) { - return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run().get()); + return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run()); } @Override public Result addPermissions(String role, Permission[]... permissions) { - return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run().get()); + return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run()); } @Override public Result removePermissions(String role, Permission... permissions) { - return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); + return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run()); } @Override public Result removePermissions(String role, Permission[]... permissions) { - return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run().get()); + return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run()); } } diff --git a/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java index 095001dce..f6161c4cd 100644 --- a/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java +++ b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java @@ -8,6 +8,7 @@ import io.weaviate.client.v1.async.users.Users; import io.weaviate.client.v1.auth.exception.AuthException; import io.weaviate.client.v1.rbac.model.Role; +import io.weaviate.client.v1.users.model.User; import io.weaviate.integration.client.async.rbac.ClientRbacTest; import io.weaviate.integration.tests.users.ClientUsersTestSuite; @@ -28,18 +29,23 @@ public ClientUsersTest(Config config, String apiKey) { } } + @Override + public Result getMyUser() { + return rethrow(() -> users.myUserGetter().run()); + } + @Override public Result> getUserRoles(String user) { - return rethrow(() -> users.userRolesGetter().withUser(user).run().get()); + return rethrow(() -> users.userRolesGetter().withUser(user).run()); } @Override public Result assignRoles(String user, String... roles) { - return rethrow(() -> this.users.assigner().withUser(user).witRoles(roles).run().get()); + return rethrow(() -> this.users.assigner().withUser(user).witRoles(roles).run()); } @Override public Result revokeRoles(String user, String... roles) { - return rethrow(() -> this.users.revoker().withUser(user).witRoles(roles).run().get()); + return rethrow(() -> this.users.revoker().withUser(user).witRoles(roles).run()); } } diff --git a/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java index 5dd61694b..b765f9a71 100644 --- a/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java +++ b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java @@ -8,6 +8,7 @@ import io.weaviate.client.v1.auth.exception.AuthException; import io.weaviate.client.v1.rbac.model.Role; import io.weaviate.client.v1.users.Users; +import io.weaviate.client.v1.users.model.User; import io.weaviate.integration.client.rbac.ClientRbacTest; import io.weaviate.integration.tests.users.ClientUsersTestSuite; @@ -23,6 +24,11 @@ public ClientUsersTest(Config config, String apiKey) { } } + @Override + public Result getMyUser() { + return users.myUserGetter().run(); + } + @Override public Result> getUserRoles(String user) { return users.userRolesGetter().withUser(user).run(); diff --git a/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java index e83036619..fa35c4c0e 100644 --- a/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java @@ -1,16 +1,16 @@ package io.weaviate.integration.tests.users; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.util.List; import java.util.function.Supplier; import org.junit.ClassRule; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.testcontainers.weaviate.WeaviateContainer; @@ -23,6 +23,7 @@ import io.weaviate.client.v1.rbac.model.Permission; import io.weaviate.client.v1.rbac.model.Role; import io.weaviate.client.v1.rbac.model.TenantsPermission; +import io.weaviate.client.v1.users.model.User; import io.weaviate.integration.client.WeaviateDockerImage; import io.weaviate.integration.client.WeaviateWithRbacContainer; import io.weaviate.integration.tests.rbac.ClientRbacTestSuite; @@ -60,30 +61,27 @@ public static Object[][] clients() { * Roles retrieved for "current user" should be identical to the ones * retrieved for them explicitly (by passing the username). */ - @Ignore // wip @DataMethod(source = ClientUsersTestSuite.class, method = "clients") @Test - public void testGetUserRoles(Supplier rbac) { - Users roles = rbac.get(); - // Result> responseCurrent = roles.getUserRoles(); - // assertThat(responseCurrent.getError()).as("get roles for current user - // error").isNull(); - // Result> responseAdminUser = roles.getUserRoles(adminUser); - // 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 testGetUserRoles(Supplier userHandle) { + Users users = userHandle.get(); + Result myUser = users.getMyUser(); + assertNull("get my user error", myUser.getError()); + Result> responseAdminUser = users.getUserRoles(adminUser); + assertNull("get roles for user error", responseAdminUser.getError()); + + List currentRoles = myUser.getResult().getRoles().values().stream().toList(); + List adminRoles = responseAdminUser.getResult(); + + Assertions.assertArrayEquals(currentRoles.toArray(), adminRoles.toArray(), + "expect same set of roles"); } /** User can be assigned a role and the role can be revoked. */ @DataMethod(source = ClientUsersTestSuite.class, method = "clients") @Test - public void testAssignRevokeRole(Supplier rbac) { - Users roles = rbac.get(); + public void testAssignRevokeRole(Supplier userHandle) { + Users roles = userHandle.get(); String myRole = roleName("VectorOwner"); try { // Arrange @@ -98,7 +96,7 @@ public void testAssignRevokeRole(Supplier rbac) { assertNull("revoke operation error", response.getError()); // Assert - assertFalse("should not have " + myRole + " role", checkHasRole(roles, adminUser, myRole)); + assertFalse(checkHasRole(roles, adminUser, myRole), "should not have " + myRole + " role"); } finally { roles.deleteRole(myRole); } @@ -122,11 +120,12 @@ private boolean checkHasRole(Users roles, String user, String role) { * functionality for creating / deleting / verifying roles. */ public interface Users extends ClientRbacTestSuite.Rbac { + Result getMyUser(); + Result> getUserRoles(String user); Result assignRoles(String user, String... roles); Result revokeRoles(String user, String... roles); } - } From 073ab9a63d73903b020bc5d8d5670c0510048cd9 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 18 Feb 2025 16:12:26 +0100 Subject: [PATCH 26/40] fix: return 'true' for all status codes < 299 Update test cases to reflect new server defaults in 1.28.6 --- .../java/io/weaviate/client/base/Result.java | 5 +- .../client/v1/rbac/model/RolesPermission.java | 6 ++- .../client/async/rbac/ClientRbacTest.java | 8 +-- .../client/rbac/ClientRbacTest.java | 8 +-- .../tests/rbac/ClientRbacTestSuite.java | 54 +++++++++++-------- 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/main/java/io/weaviate/client/base/Result.java b/src/main/java/io/weaviate/client/base/Result.java index a84e3cd66..a61397f95 100644 --- a/src/main/java/io/weaviate/client/base/Result.java +++ b/src/main/java/io/weaviate/client/base/Result.java @@ -7,7 +7,6 @@ import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.HttpStatus; import io.weaviate.client.base.http.async.ResponseParser; import lombok.AccessLevel; @@ -69,7 +68,7 @@ public Result toErrorResult() { */ public static Result voidToBoolean(Response response) { int status = response.getStatusCode(); - return new Result<>(status, status == 200, response.getErrors()); + return new Result<>(status, status < 299, response.getErrors()); } /** @@ -83,7 +82,7 @@ public static ResponseParser voidToBooleanParser() { @Override public Result parse(HttpResponse response, String body, ContentType contentType) { Response resp = this.serializer.toResponse(response.getCode(), body, Object.class); - return new Result<>(resp.getStatusCode(), resp.getStatusCode() == HttpStatus.SC_OK, resp.getErrors()); + return new Result<>(resp.getStatusCode(), resp.getStatusCode() < 299, resp.getErrors()); } }; } 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 index e35e54666..45c46c146 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -39,7 +39,11 @@ public enum Action implements RbacAction { UPDATE("update_roles"), DELETE("delete_roles"), - /* Backward compatibility with 1.28. */ + /* + * DO NOT CREATE NEW PERMISSIONS WITH THIS ACTION. + * It is preserved for backward compatibility with 1.28 + * and should only be used internally to read legacy permissions. + */ @Deprecated MANAGE("manage_roles"); diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java index 0a74f2f9b..75185f2ba 100644 --- a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -60,13 +60,13 @@ public Result> getAssignedUsers(String role) { } @Override - public void createRole(String role, Permission... permissions) { - rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); + public Result createRole(String role, Permission... permissions) { + return rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); } @Override - public void createRole(String role, Permission[]... permissions) { - rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); + public Result createRole(String role, Permission[]... permissions) { + return rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); } @Override diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 6540c47e5..3922e88f0 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -38,13 +38,13 @@ public Result> getAssignedUsers(String role) { } @Override - public void createRole(String role, Permission... permissions) { - roles.creator().withName(role).withPermissions(permissions).run(); + public Result createRole(String role, Permission... permissions) { + return roles.creator().withName(role).withPermissions(permissions).run(); } @Override - public void createRole(String role, Permission[]... permissions) { - roles.creator().withName(role).withPermissions(permissions).run(); + public Result createRole(String role, Permission[]... permissions) { + return roles.creator().withName(role).withPermissions(permissions).run(); } @Override diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 7a1ac47ae..4b29b28a3 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -18,6 +18,7 @@ import com.jparams.junit4.JParamsTestRunner; import com.jparams.junit4.data.DataMethod; +import com.jparams.junit4.description.Name; import io.weaviate.client.Config; import io.weaviate.client.base.Result; @@ -37,6 +38,7 @@ public class ClientRbacTestSuite { private static final String adminRole = "admin"; + private static final String rootRole = "root"; private static final String viewerRole = "viewer"; private static final String adminUser = "john-doe"; private static final String API_KEY = WeaviateWithRbacContainer.makeSecret(adminUser); @@ -56,8 +58,10 @@ public static Config config() { public static Object[][] clients() { try { return new Object[][] { - { (Supplier) () -> new io.weaviate.integration.client.rbac.ClientRbacTest(config(), API_KEY) }, - { (Supplier) () -> new io.weaviate.integration.client.async.rbac.ClientRbacTest(config(), API_KEY) } + { "sync", + (Supplier) () -> new io.weaviate.integration.client.rbac.ClientRbacTest(config(), API_KEY) }, + { "async", + (Supplier) () -> new io.weaviate.integration.client.async.rbac.ClientRbacTest(config(), API_KEY) } }; } catch (Exception e) { throw new RuntimeException(e); @@ -69,29 +73,32 @@ public static Object[][] clients() { * will have 'admin' and 'viewer' roles. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testGetAll(Supplier rbac) { + public void testGetAll(String _name, Supplier rbac) { Rbac roles = rbac.get(); Result> response = roles.getAll(); List all = response.getResult(); assertThat(response.getError()).as("get all roles error").isNull(); - assertThat(all).hasSize(2).as("wrong number of roles"); + assertThat(all).hasSize(3).as("wrong number of roles"); assertThat(all.get(0)).returns(adminRole, Role::getName); - assertThat(all.get(1)).returns(viewerRole, Role::getName); + assertThat(all.get(1)).returns(rootRole, Role::getName); + assertThat(all.get(2)).returns(viewerRole, Role::getName); } /** Admin user should have the admin role assigned to them. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testGetAssignedUsers(Supplier rbac) { + public void testGetAssignedUsers(String _name, Supplier rbac) { Rbac roles = rbac.get(); - Result> response = roles.getAssignedUsers(adminRole); + Result> response = roles.getAssignedUsers(rootRole); 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"); + assertThat(users).as("users assigned to " + rootRole + " role").hasSize(1); + assertEquals(adminUser, users.get(0), "wrong user assinged to " + rootRole + " role"); } /** @@ -99,8 +106,9 @@ public void testGetAssignedUsers(Supplier rbac) { * Tests addition and fetching the role to. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testCreate(Supplier rbac) { + public void testCreate(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); String myCollection = "Pizza"; @@ -109,7 +117,7 @@ public void testCreate(Supplier rbac) { Permission.backups(BackupsPermission.Action.MANAGE, myCollection), Permission.cluster(ClusterPermission.Action.READ), Permission.nodes(myCollection, NodesPermission.Action.READ), - Permission.roles(viewerRole, RolesPermission.Action.MANAGE), + Permission.roles(viewerRole, RolesPermission.Action.CREATE), Permission.collections(myCollection, CollectionsPermission.Action.CREATE), Permission.data(myCollection, DataPermission.Action.UPDATE), Permission.tenants(TenantsPermission.Action.DELETE), @@ -120,7 +128,9 @@ public void testCreate(Supplier rbac) { roles.deleteRole(myRole); // Act - roles.createRole(myRole, wantPermissions); + Result create = roles.createRole(myRole, wantPermissions); + assertNull("error creating role", create.getError()); + assertTrue("created successfully", create.getResult()); Result response = roles.getRole(myRole); Role role = response.getResult(); @@ -141,8 +151,9 @@ public void testCreate(Supplier rbac) { * behavior because it is the server's responsibility. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testAddPermissions(Supplier rbac) { + public void testAddPermissions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); Permission toAdd = Permission.cluster(ClusterPermission.Action.READ)[0]; @@ -167,8 +178,9 @@ public void testAddPermissions(Supplier rbac) { * with multiple actions. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testAddPermissionsMultipleActions(Supplier rbac) { + public void testAddPermissionsMultipleActions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); Permission[] toAdd = Permission.data("Pizza", @@ -199,8 +211,9 @@ public void testAddPermissionsMultipleActions(Supplier rbac) { * responsibility. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testRemovePermissions(Supplier rbac) { + public void testRemovePermissions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE)[0]; @@ -229,8 +242,9 @@ public void testRemovePermissions(Supplier rbac) { * with multiple actions. */ @DataMethod(source = ClientRbacTestSuite.class, method = "clients") + @Name("{class}/client={0} ") @Test - public void testRemovePermissionsMultipleAction(Supplier rbac) { + public void testRemovePermissionsMultipleAction(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); Permission[] toRemove = Permission.data("Pizza", @@ -268,10 +282,6 @@ private boolean checkHasPermission(Rbac roles, String role, Permission> getAssignedUsers(String role); - void createRole(String role, Permission... permissions); + Result createRole(String role, Permission... permissions); - void createRole(String role, Permission[]... permissions); + Result createRole(String role, Permission[]... permissions); void deleteRole(String role); From bf175e87217ccf538099e6666862757d5cead2c3 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 18 Feb 2025 17:19:46 +0100 Subject: [PATCH 27/40] feat: do not sent requests with deprecated actions to the server Some actions are hard-deprecated and will result in an error. They are only used by the library internally to read legacy permissions. --- .../client/v1/rbac/model/Permission.java | 46 +++++++------------ .../client/v1/rbac/model/RbacAction.java | 4 ++ .../client/v1/rbac/model/RolesPermission.java | 12 ++++- .../client/v1/rbac/model/PermissionTest.java | 23 +++++++++- 4 files changed, 52 insertions(+), 33 deletions(-) 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 index 6bc1278ec..e14a18a52 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -54,6 +54,7 @@ public static Permission fromWeaviate(WeaviatePermission perm) { * {@code Permission.backups(BackupsPermission.Action.MANAGE, "Pizza") } */ public static BackupsPermission[] backups(BackupsPermission.Action action, String collection) { + checkDeprecation(action); return new BackupsPermission[] { new BackupsPermission(collection, action) }; } @@ -63,19 +64,10 @@ public static BackupsPermission[] backups(BackupsPermission.Action action, Strin * Example: {@code Permission.cluster(ClusterPermission.Action.READ, "Pizza") } */ public static ClusterPermission[] cluster(ClusterPermission.Action action) { + checkDeprecation(action); return new ClusterPermission[] { new ClusterPermission(action) }; } - /** - * Create permission for managing collection's configuration. - *

- * Example: - * {@code Permission.collections("Pizza", CollectionsPermission.Action.READ) } - */ - public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action action) { - return new CollectionsPermission[] { new CollectionsPermission(collection, action) }; - } - /** * Create permission for collection's configuration. *

@@ -85,21 +77,12 @@ public static CollectionsPermission[] collections(String collection, Collections public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action... actions) { CollectionsPermission[] permissions = new CollectionsPermission[actions.length]; for (int i = 0; i < actions.length; i++) { + checkDeprecation(actions[i]); permissions[i] = new CollectionsPermission(collection, actions[i]); } return permissions; } - /** - * Create permissions for multiple actions for managing collection's data. - *

- * Example: - * {@code Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.UPDATE) } - */ - public static DataPermission[] data(String collection, DataPermission.Action action) { - return new DataPermission[] { new DataPermission(collection, action) }; - } - /** * Create permissions for multiple actions for managing collection's * data. @@ -110,6 +93,7 @@ public static DataPermission[] data(String collection, DataPermission.Action act public static DataPermission[] data(String collection, DataPermission.Action... actions) { DataPermission[] permissions = new DataPermission[actions.length]; for (int i = 0; i < actions.length; i++) { + checkDeprecation(actions[i]); permissions[i] = new DataPermission(collection, actions[i]); } return permissions; @@ -133,19 +117,10 @@ public static NodesPermission[] nodes(NodesPermission.Verbosity verbosity, Nodes * {@code Permission.nodes("Pizza", NodesPermission.Action.READ) } */ public static NodesPermission[] nodes(String collection, NodesPermission.Action action) { + checkDeprecation(action); return new NodesPermission[] { new NodesPermission(collection, action) }; } - /** - * Create {@link RolesPermission} for a role. - *

- * Example: - * {@code Permission.roles("MyRole", RolesPermission.Action.READ) } - */ - public static RolesPermission[] roles(String role, RolesPermission.Action action) { - return new RolesPermission[] { new RolesPermission(role, action) }; - } - /** * Create {@link RolesPermission} for multiple actions. *

@@ -155,6 +130,7 @@ public static RolesPermission[] roles(String role, RolesPermission.Action action public static RolesPermission[] roles(String role, RolesPermission.Action... actions) { RolesPermission[] permissions = new RolesPermission[actions.length]; for (int i = 0; i < actions.length; i++) { + checkDeprecation(actions[i]); permissions[i] = new RolesPermission(role, actions[i]); } return permissions; @@ -167,6 +143,7 @@ public static RolesPermission[] roles(String role, RolesPermission.Action... act * {@code Permission.tenants(TenantsPermission.Action.READ) } */ public static TenantsPermission[] tenants(TenantsPermission.Action action) { + checkDeprecation(action); return new TenantsPermission[] { new TenantsPermission(action) }; } @@ -177,10 +154,19 @@ public static TenantsPermission[] tenants(TenantsPermission.Action action) { * {@code Permission.users(UsersPermission.Action.READ) } */ public static UsersPermission[] users(UsersPermission.Action action) { + checkDeprecation(action); return new UsersPermission[] { new UsersPermission(action) }; } + private static void checkDeprecation(RbacAction action) throws IllegalArgumentException { + if (action.isDeprecated()) { + throw new IllegalArgumentException(action.getValue() + + " is hard-deprecated and should only be used to read legacy permissions created in v1.28"); + } + } + public String toString() { return String.format("Permission", this.action); } + } diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java index 15c326dcf..750937f85 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java @@ -24,6 +24,10 @@ interface RbacAction { String getValue(); + default boolean isDeprecated() { + return false; + } + static & RbacAction> E fromString(Class enumClass, String value) throws IllegalArgumentException { for (E action : enumClass.getEnumConstants()) { 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 index 45c46c146..89c2678e6 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -45,10 +45,20 @@ public enum Action implements RbacAction { * and should only be used internally to read legacy permissions. */ @Deprecated - MANAGE("manage_roles"); + MANAGE("manage_roles", true); + + Action(String value) { + this(value, false); + } @Getter private final String value; + + private final boolean deprecated; + + public boolean isDeprecated() { + return deprecated; + } } public enum Scope { 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 index 2ed2c433d..68226e935 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -1,6 +1,7 @@ package io.weaviate.client.v1.rbac.model; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.util.ArrayList; @@ -8,6 +9,7 @@ import java.util.function.Supplier; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import org.junit.runner.RunWith; import org.testcontainers.shaded.org.hamcrest.Matcher; import org.testcontainers.shaded.org.hamcrest.MatcherAssert; @@ -24,7 +26,7 @@ public static Object[][] serializationTestCases() { BackupsPermission backups = new BackupsPermission("Pizza", BackupsPermission.Action.MANAGE); DataPermission data = new DataPermission("Pizza", DataPermission.Action.MANAGE); NodesPermission nodes = new NodesPermission("Pizza", NodesPermission.Action.READ); - RolesPermission roles = new RolesPermission("TestWriter", RolesPermission.Action.MANAGE); + RolesPermission roles = new RolesPermission("TestWriter", RolesPermission.Action.CREATE); CollectionsPermission collections = new CollectionsPermission("Pizza", CollectionsPermission.Action.CREATE); ClusterPermission cluster = new ClusterPermission(ClusterPermission.Action.READ); TenantsPermission tenants = new TenantsPermission(TenantsPermission.Action.READ); @@ -49,7 +51,7 @@ public static Object[][] serializationTestCases() { { "roles permission", (Supplier>) () -> roles, - new WeaviatePermission("manage_roles", roles), + new WeaviatePermission("create_roles", roles), }, { "collections permission", @@ -181,4 +183,21 @@ public void testGroupedConstructors(Permission>[] permis Object[] actualActions = Arrays.stream(permissions).map(Permission::getAction).toArray(); assertArrayEquals(expectedActions, actualActions, "set of allowed actions do not match"); } + + public static Object[][] deprecatedActions() { + return new Object[][] { + { (ThrowingRunnable) () -> Permission.roles("AnyRole", RolesPermission.Action.MANAGE) }, + }; + } + + /** + * Passing deprecated actions, e.g {@link RolesPermission.Action.MANAGE}, will + * result in an error, and we can prevent that sooner. + */ + @DataMethod(source = PermissionTest.class, method = "deprecatedActions") + @Test + public void testDeprecatedActions(ThrowingRunnable r) { + assertThrows(IllegalArgumentException.class, r); + + } } From 3b21ed0efc394984982ae5ccda3b207b0c04e432 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 18 Feb 2025 17:29:25 +0100 Subject: [PATCH 28/40] fix: collect streams using Collectors.toList Java 8 does not have Stream::toList. --- .../io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java | 3 ++- .../weaviate/client/v1/async/users/api/UserRolesGetter.java | 3 ++- .../java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java | 3 ++- .../io/weaviate/client/v1/rbac/api/WeaviatePermission.java | 3 ++- .../java/io/weaviate/client/v1/rbac/api/WeaviateRole.java | 3 ++- src/main/java/io/weaviate/client/v1/rbac/model/Role.java | 4 +++- .../java/io/weaviate/client/v1/users/api/UserRolesGetter.java | 3 ++- .../java/io/weaviate/client/v1/users/api/WeaviateUser.java | 3 ++- .../integration/tests/users/ClientUsersTestSuite.java | 3 ++- 9 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java index 26110ebcf..376683697 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/RoleAllGetter.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Future; +import java.util.stream.Collectors; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.core5.concurrent.FutureCallback; @@ -38,7 +39,7 @@ public Result> parse(HttpResponse response, String body, ContentType .orElse(new ArrayList<>()) .stream() .map(w -> w.toRole()) - .toList(); + .collect(Collectors.toList()); return new Result<>(resp.getStatusCode(), roles, resp.getErrors()); } }); diff --git a/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java index 92aec8f47..a71334b2f 100644 --- a/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Future; +import java.util.stream.Collectors; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.core5.concurrent.FutureCallback; @@ -45,7 +46,7 @@ public Result> parse(HttpResponse response, String body, ContentType .orElse(new ArrayList<>()) .stream() .map(w -> w.toRole()) - .toList(); + .collect(Collectors.toList()); return new Result<>(resp.getStatusCode(), roles, resp.getErrors()); } }); 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 index fe1f55bdc..4519745d1 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import io.weaviate.client.Config; import io.weaviate.client.base.BaseClient; @@ -27,7 +28,7 @@ public Result> run() { .orElse(new ArrayList<>()) .stream() .map(w -> w.toRole()) - .toList(); + .collect(Collectors.toList()); return new Result<>(resp.getStatusCode(), roles, resp.getErrors()); } } 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 index 98fa65ba6..40c0545ac 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -1,6 +1,7 @@ package io.weaviate.client.v1.rbac.api; import java.util.List; +import java.util.stream.Collectors; import io.weaviate.client.v1.rbac.model.BackupsPermission; import io.weaviate.client.v1.rbac.model.CollectionsPermission; @@ -48,6 +49,6 @@ public

> WeaviatePermission(String action, Permission

} public static List mergePermissions(List> permissions) { - return permissions.stream().map(perm -> perm.toWeaviate()).toList(); + return permissions.stream().map(perm -> perm.toWeaviate()).collect(Collectors.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 index ae24052a1..8e7080a4f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -1,6 +1,7 @@ package io.weaviate.client.v1.rbac.api; import java.util.List; +import java.util.stream.Collectors; import io.weaviate.client.v1.rbac.model.Permission; import io.weaviate.client.v1.rbac.model.Role; @@ -20,7 +21,7 @@ public WeaviateRole(String name, List> permissions) { /** Create {@link Role} from the API response object. */ public Role toRole() { List> permissions = this.permissions.stream() - .>map(perm -> Permission.fromWeaviate(perm)).toList(); + .>map(perm -> Permission.fromWeaviate(perm)).collect(Collectors.toList()); return new Role(this.name, permissions); } } 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 index 21597d0fc..ac0ef33d8 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Role.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -19,6 +20,7 @@ public String toString() { "Role", this.name, permissions.isEmpty() ? "none" - : String.join(", ", permissions.stream().map(Permission::getAction).toList())); + : String.join(", ", permissions.stream().map(Permission::getAction) + .collect(Collectors.toList()))); } } diff --git a/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java index 2d42c3591..491ab44f4 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java @@ -4,6 +4,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import io.weaviate.client.Config; import io.weaviate.client.base.BaseClient; @@ -35,7 +36,7 @@ public Result> run() { .orElse(new ArrayList<>()) .stream() .map(w -> w.toRole()) - .toList(); + .collect(Collectors.toList()); return new Result<>(resp.getStatusCode(), roles, resp.getErrors()); } diff --git a/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java index c1e2ede99..1f2919a02 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java +++ b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java @@ -1,6 +1,7 @@ package io.weaviate.client.v1.users.api; import java.util.List; +import java.util.stream.Collectors; import io.weaviate.client.v1.rbac.api.WeaviateRole; import io.weaviate.client.v1.users.model.User; @@ -11,6 +12,6 @@ public class WeaviateUser { List roles; public User toUser() { - return new User(name, id, roles.stream().map(WeaviateRole::toRole).toList()); + return new User(name, id, roles.stream().map(WeaviateRole::toRole).collect(Collectors.toList())); } } diff --git a/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java index fa35c4c0e..651144e6a 100644 --- a/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/users/ClientUsersTestSuite.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.junit.ClassRule; import org.junit.Rule; @@ -70,7 +71,7 @@ public void testGetUserRoles(Supplier userHandle) { Result> responseAdminUser = users.getUserRoles(adminUser); assertNull("get roles for user error", responseAdminUser.getError()); - List currentRoles = myUser.getResult().getRoles().values().stream().toList(); + List currentRoles = myUser.getResult().getRoles().values().stream().collect(Collectors.toList()); List adminRoles = responseAdminUser.getResult(); Assertions.assertArrayEquals(currentRoles.toArray(), adminRoles.toArray(), From f45572e7077f0d9f5ebe284fdc10e89a606b6860 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 18 Feb 2025 17:42:50 +0100 Subject: [PATCH 29/40] refactor: simplify deprecated actions --- .../client/v1/rbac/model/RbacAction.java | 13 +++++++++++++ .../client/v1/rbac/model/RolesPermission.java | 17 ++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java index 750937f85..4a25baf3a 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RbacAction.java @@ -24,6 +24,19 @@ interface RbacAction { String getValue(); + /** + * Returns true if the action is hard deprecated. + * + *

+ * Override default return for a deprecated enum value like so: + * + *

{@code
+   * OLD_ACTION("old_action") {
+   *  @Override
+   *  public boolean isDeprecated() { return true; }
+   * };
+   * }
+ */ default boolean isDeprecated() { return false; } 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 index 89c2678e6..ef671dfe3 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -45,20 +45,15 @@ public enum Action implements RbacAction { * and should only be used internally to read legacy permissions. */ @Deprecated - MANAGE("manage_roles", true); - - Action(String value) { - this(value, false); - } + MANAGE("manage_roles") { + @Override + public boolean isDeprecated() { + return true; + }; + }; @Getter private final String value; - - private final boolean deprecated; - - public boolean isDeprecated() { - return deprecated; - } } public enum Scope { From 7068e572e5ce0a48b5da23dabad35117e2714bf3 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 19 Feb 2025 17:58:12 +0100 Subject: [PATCH 30/40] refactor: action argument should go last in Permission.backups to be consistent --- src/main/java/io/weaviate/client/v1/rbac/model/Permission.java | 2 +- .../io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index e14a18a52..27da9aba4 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -53,7 +53,7 @@ public static Permission fromWeaviate(WeaviatePermission perm) { * Example: * {@code Permission.backups(BackupsPermission.Action.MANAGE, "Pizza") } */ - public static BackupsPermission[] backups(BackupsPermission.Action action, String collection) { + public static BackupsPermission[] backups(String collection, BackupsPermission.Action action) { checkDeprecation(action); return new BackupsPermission[] { new BackupsPermission(collection, action) }; } diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index 4b29b28a3..a3ad64420 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -114,7 +114,7 @@ public void testCreate(String _name, Supplier rbac) { String myCollection = "Pizza"; Permission[][] wantPermissions = new Permission[][] { - Permission.backups(BackupsPermission.Action.MANAGE, myCollection), + Permission.backups(myCollection, BackupsPermission.Action.MANAGE), Permission.cluster(ClusterPermission.Action.READ), Permission.nodes(myCollection, NodesPermission.Action.READ), Permission.roles(viewerRole, RolesPermission.Action.CREATE), From 65b3079cd9e785e721846ffbba16ad7e7a1336bc Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 21 Feb 2025 11:12:20 +0100 Subject: [PATCH 31/40] ci: bump WEAVIATE_VERSION to 1.29.0 --- .../io/weaviate/integration/client/WeaviateVersion.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index eb5d37bca..9fa039570 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.28.6-660c1fa"; + public static final String WEAVIATE_IMAGE = "1.29.0"; // to be set according to weaviate docker image - public static final String EXPECTED_WEAVIATE_VERSION = "1.28.6"; + public static final String EXPECTED_WEAVIATE_VERSION = "1.29.0"; // to be set according to weaviate docker image - public static final String EXPECTED_WEAVIATE_GIT_HASH = "660c1fa"; + public static final String EXPECTED_WEAVIATE_GIT_HASH = "8f0e033"; private WeaviateVersion() { } From 2d6d97b6712aa8a8c3aa4891091787065b2d988a Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 24 Feb 2025 17:37:21 +0100 Subject: [PATCH 32/40] chore: remove 'tenant' and 'object' filters from permissions --- .../v1/rbac/model/CollectionsPermission.java | 10 ++------ .../client/v1/rbac/model/DataPermission.java | 12 ++-------- .../v1/rbac/model/TenantsPermission.java | 8 +------ .../client/v1/rbac/model/PermissionTest.java | 24 +------------------ 4 files changed, 6 insertions(+), 48 deletions(-) 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 index a6eca347d..74112ef60 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -9,22 +9,16 @@ @EqualsAndHashCode(callSuper = true) public class CollectionsPermission extends Permission { final String collection; - final String tenant; public CollectionsPermission(String collection, Action action) { - this(collection, "*", action); + super(action); + this.collection = collection; } CollectionsPermission(String collection, String action) { this(collection, RbacAction.fromString(Action.class, action)); } - private CollectionsPermission(String collection, String tenant, Action action) { - super(action); - this.collection = collection; - this.tenant = tenant; - } - @Override public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); 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 index 8037f8232..c1e839508 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -9,24 +9,16 @@ @EqualsAndHashCode(callSuper = true) public class DataPermission extends Permission { final String collection; - final String object; - final String tenant; public DataPermission(String collection, Action action) { - this(collection, "*", "*", action); + super(action); + this.collection = collection; } DataPermission(String collection, String action) { this(collection, RbacAction.fromString(Action.class, action)); } - private DataPermission(String collection, String object, String tenant, Action action) { - super(action); - this.collection = collection; - this.object = object; - this.tenant = tenant; - } - @Override public WeaviatePermission toWeaviate() { return new WeaviatePermission(this.action, this); 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 index 373157188..efc8711b5 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -8,21 +8,15 @@ @Getter @EqualsAndHashCode(callSuper = true) public class TenantsPermission extends Permission { - final String tenant; public TenantsPermission(Action action) { - this(action, "*"); + super(action); } TenantsPermission(String action) { this(RbacAction.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); 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 index 68226e935..702cc04d2 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -88,21 +88,6 @@ private static Matcher sameAs(T expected) { return new SamePropertyValuesAs(expected, new ArrayList<>()); } - @Test - public void testDefaultDataPermission() { - DataPermission perm = new DataPermission("Pizza", DataPermission.Action.MANAGE); - assertThat(perm).as("data permission must have object=* and tenant=*") - .returns("*", DataPermission::getObject) - .returns("*", DataPermission::getTenant); - } - - @Test - public void testDefaultCollectionsPermission() { - CollectionsPermission perm = new CollectionsPermission("Pizza", CollectionsPermission.Action.CREATE); - assertThat(perm).as("collection permission must have tenant=*") - .returns("*", CollectionsPermission::getTenant); - } - @Test public void testDefaultNodesPermission() { NodesPermission perm = new NodesPermission(NodesPermission.Verbosity.MINIMAL, NodesPermission.Action.READ); @@ -110,17 +95,10 @@ public void testDefaultNodesPermission() { .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); - } - @Test public void testDefaultRolesPermission() { RolesPermission perm = new RolesPermission("ExampleRole", RolesPermission.Action.READ); - assertThat(perm).as("tenants permission must have scope=null") + assertThat(perm).as("roles permission must have scope=null") .returns(null, RolesPermission::getScope); } From bbd6c4744aae24f7222196852f428dcc0fc3dd27 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 24 Feb 2025 17:46:30 +0100 Subject: [PATCH 33/40] chore: rename withUser -> withUserId --- .../weaviate/client/v1/async/users/api/RoleAssigner.java | 8 ++++---- .../weaviate/client/v1/async/users/api/RoleRevoker.java | 8 ++++---- .../client/v1/async/users/api/UserRolesGetter.java | 8 ++++---- .../io/weaviate/client/v1/users/api/RoleAssigner.java | 8 ++++---- .../java/io/weaviate/client/v1/users/api/RoleRevoker.java | 8 ++++---- .../io/weaviate/client/v1/users/api/UserRolesGetter.java | 8 ++++---- .../integration/client/async/users/ClientUsersTest.java | 6 +++--- .../integration/client/users/ClientUsersTest.java | 6 +++--- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java index 885844a6b..8d907efe6 100644 --- a/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/RoleAssigner.java @@ -16,15 +16,15 @@ import lombok.AllArgsConstructor; public class RoleAssigner extends AsyncBaseClient implements AsyncClientResult { - private String user; + private String userId; private List roles = new ArrayList<>(); public RoleAssigner(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { super(httpClient, config, tokenProvider); } - public RoleAssigner withUser(String user) { - this.user = user; + public RoleAssigner withUserId(String id) { + this.userId = id; return this; } @@ -45,6 +45,6 @@ public Future> run(FutureCallback> callback) { } private String path() { - return String.format("/authz/users/%s/assign", this.user); + return String.format("/authz/users/%s/assign", this.userId); } } diff --git a/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java index 6943e1443..81b1ec4e1 100644 --- a/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/RoleRevoker.java @@ -17,15 +17,15 @@ import lombok.AllArgsConstructor; public class RoleRevoker extends AsyncBaseClient implements AsyncClientResult { - private String user; + private String userId; private List roles = new ArrayList<>(); public RoleRevoker(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { super(httpClient, config, tokenProvider); } - public RoleRevoker withUser(String user) { - this.user = user; + public RoleRevoker withUserId(String id) { + this.userId = id; return this; } @@ -46,6 +46,6 @@ public Future> run(FutureCallback> callback) { } private String path() { - return String.format("/authz/users/%s/revoke", this.user); + return String.format("/authz/users/%s/revoke", this.userId); } } diff --git a/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java index a71334b2f..67d3d8024 100644 --- a/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/async/users/api/UserRolesGetter.java @@ -23,15 +23,15 @@ import io.weaviate.client.v1.rbac.model.Role; public class UserRolesGetter extends AsyncBaseClient> implements AsyncClientResult> { - private String user; + private String userId; public UserRolesGetter(CloseableHttpAsyncClient httpClient, Config config, AccessTokenProvider tokenProvider) { super(httpClient, config, tokenProvider); } /** Leave unset to fetch roles assigned to the current user. */ - public UserRolesGetter withUser(String user) { - this.user = user; + public UserRolesGetter withUserId(String id) { + this.userId = id; return this; } @@ -53,6 +53,6 @@ public Result> parse(HttpResponse response, String body, ContentType } private String path() { - return String.format("/authz/users/%s/roles", this.user); + return String.format("/authz/users/%s/roles", this.userId); } } diff --git a/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java index 2a884a605..e407a2bc5 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java +++ b/src/main/java/io/weaviate/client/v1/users/api/RoleAssigner.java @@ -13,15 +13,15 @@ import lombok.AllArgsConstructor; public class RoleAssigner extends BaseClient implements ClientResult { - private String user; + private String userId; private List roles = new ArrayList<>(); public RoleAssigner(HttpClient httpClient, Config config) { super(httpClient, config); } - public RoleAssigner withUser(String user) { - this.user = user; + public RoleAssigner withUserId(String id) { + this.userId = id; return this; } @@ -44,6 +44,6 @@ public Result run() { } private String path() { - return String.format("/authz/users/%s/assign", this.user); + return String.format("/authz/users/%s/assign", this.userId); } } diff --git a/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java index bbb2d888b..236902f56 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java +++ b/src/main/java/io/weaviate/client/v1/users/api/RoleRevoker.java @@ -13,15 +13,15 @@ import lombok.AllArgsConstructor; public class RoleRevoker extends BaseClient implements ClientResult { - private String user; + private String userId; private List roles = new ArrayList<>(); public RoleRevoker(HttpClient httpClient, Config config) { super(httpClient, config); } - public RoleRevoker withUser(String user) { - this.user = user; + public RoleRevoker withUserId(String id) { + this.userId = id; return this; } @@ -42,6 +42,6 @@ public Result run() { } private String path() { - return String.format("/authz/users/%s/revoke", this.user); + return String.format("/authz/users/%s/revoke", this.userId); } } diff --git a/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java index 491ab44f4..1dd33f112 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java +++ b/src/main/java/io/weaviate/client/v1/users/api/UserRolesGetter.java @@ -16,15 +16,15 @@ import io.weaviate.client.v1.rbac.model.Role; public class UserRolesGetter extends BaseClient implements ClientResult> { - private String user; + private String userId; 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; + public UserRolesGetter withUserId(String id) { + this.userId = id; return this; } @@ -41,6 +41,6 @@ public Result> run() { } private String path() { - return String.format("/authz/users/%s/roles", this.user); + return String.format("/authz/users/%s/roles", this.userId); } } diff --git a/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java index f6161c4cd..b83db6576 100644 --- a/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java +++ b/src/test/java/io/weaviate/integration/client/async/users/ClientUsersTest.java @@ -36,16 +36,16 @@ public Result getMyUser() { @Override public Result> getUserRoles(String user) { - return rethrow(() -> users.userRolesGetter().withUser(user).run()); + return rethrow(() -> users.userRolesGetter().withUserId(user).run()); } @Override public Result assignRoles(String user, String... roles) { - return rethrow(() -> this.users.assigner().withUser(user).witRoles(roles).run()); + return rethrow(() -> this.users.assigner().withUserId(user).witRoles(roles).run()); } @Override public Result revokeRoles(String user, String... roles) { - return rethrow(() -> this.users.revoker().withUser(user).witRoles(roles).run()); + return rethrow(() -> this.users.revoker().withUserId(user).witRoles(roles).run()); } } diff --git a/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java index b765f9a71..e9e49eb3a 100644 --- a/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java +++ b/src/test/java/io/weaviate/integration/client/users/ClientUsersTest.java @@ -31,16 +31,16 @@ public Result getMyUser() { @Override public Result> getUserRoles(String user) { - return users.userRolesGetter().withUser(user).run(); + return users.userRolesGetter().withUserId(user).run(); } @Override public Result assignRoles(String user, String... roles) { - return this.users.assigner().withUser(user).witRoles(roles).run(); + return this.users.assigner().withUserId(user).witRoles(roles).run(); } @Override public Result revokeRoles(String user, String... roles) { - return this.users.revoker().withUser(user).witRoles(roles).run(); + return this.users.revoker().withUserId(user).witRoles(roles).run(); } } From 1d80eb5952629fb6ed663375e0a2299fad892bce Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 24 Feb 2025 18:11:54 +0100 Subject: [PATCH 34/40] feat: read username into userId field if user_id not available --- .../client/v1/users/api/WeaviateUser.java | 15 ++++-- .../weaviate/client/v1/users/model/User.java | 7 ++- .../client/v1/users/api/WeaviateUserTest.java | 46 +++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/test/java/io/weaviate/client/v1/users/api/WeaviateUserTest.java diff --git a/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java index 1f2919a02..25713afcb 100644 --- a/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java +++ b/src/main/java/io/weaviate/client/v1/users/api/WeaviateUser.java @@ -1,17 +1,26 @@ package io.weaviate.client.v1.users.api; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import com.google.gson.annotations.SerializedName; + import io.weaviate.client.v1.rbac.api.WeaviateRole; import io.weaviate.client.v1.users.model.User; public class WeaviateUser { - String name; + @SerializedName("username") + String username; + + @SerializedName("user_id") String id; - List roles; + + @SerializedName("roles") + List roles = new ArrayList<>(); public User toUser() { - return new User(name, id, roles.stream().map(WeaviateRole::toRole).collect(Collectors.toList())); + return new User(id != null ? id : username, + roles.stream().map(WeaviateRole::toRole).collect(Collectors.toList())); } } diff --git a/src/main/java/io/weaviate/client/v1/users/model/User.java b/src/main/java/io/weaviate/client/v1/users/model/User.java index 52023e6a0..436eb4b13 100644 --- a/src/main/java/io/weaviate/client/v1/users/model/User.java +++ b/src/main/java/io/weaviate/client/v1/users/model/User.java @@ -1,5 +1,6 @@ package io.weaviate.client.v1.users.model; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -9,12 +10,10 @@ @Getter public class User { - String name; String userId; - Map roles; + Map roles = new HashMap<>(); - public User(String name, String id, List roles) { - this.name = name; + public User(String id, List roles) { this.userId = id; this.roles = roles.stream().collect(Collectors.toMap(Role::getName, r -> r)); } diff --git a/src/test/java/io/weaviate/client/v1/users/api/WeaviateUserTest.java b/src/test/java/io/weaviate/client/v1/users/api/WeaviateUserTest.java new file mode 100644 index 000000000..2935a2162 --- /dev/null +++ b/src/test/java/io/weaviate/client/v1/users/api/WeaviateUserTest.java @@ -0,0 +1,46 @@ +package io.weaviate.client.v1.users.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.jparams.junit4.JParamsTestRunner; +import com.jparams.junit4.data.DataMethod; + +import io.weaviate.client.base.Serializer; +import io.weaviate.client.v1.users.model.User; + +@RunWith(JParamsTestRunner.class) +public class WeaviateUserTest { + private final Serializer ser = new Serializer(); + + public static Object[][] deserializationTestCases() { + return new Object[][] { + { + "has username, no user_id", + "{\"username\": \"John Doe\"}", + new User("John Doe", new ArrayList<>()), + }, + { + "has user_id, no username", + "{\"user_id\": \"john_doe\"}", + new User("john_doe", new ArrayList<>()), + }, + { + "has both user_id and username", + "{\"user_id\": \"john_doe\", \"username\": \"John Doe\"}", + new User("john_doe", new ArrayList<>()), + }, + }; + } + + @DataMethod(source = WeaviateUserTest.class, method = "deserializationTestCases") + @Test + public void testToUser(String name, String json, User want) { + User got = ser.toResponse(json, WeaviateUser.class).toUser(); + assertEquals(want.getUserId(), got.getUserId(), "user id"); + } +} From 53179f847ed55b53be92c14268d7b6a5143e7a10 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 26 Feb 2025 13:54:20 +0100 Subject: [PATCH 35/40] ci: fix expected Weaviate version --- .../java/io/weaviate/integration/client/WeaviateVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java index 9fa039570..32cfc3ffa 100644 --- a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java +++ b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java @@ -8,7 +8,7 @@ public class WeaviateVersion { // to be set according to weaviate docker image public static final String EXPECTED_WEAVIATE_VERSION = "1.29.0"; // 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 = "35d800d"; private WeaviateVersion() { } From 2b7a1f26947ea279b650da19d8dd55dcf5de24ad Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 26 Feb 2025 15:01:51 +0100 Subject: [PATCH 36/40] refactor: push toWeaviate() to the parent Permission class --- .../io/weaviate/client/v1/rbac/model/BackupsPermission.java | 6 ------ .../io/weaviate/client/v1/rbac/model/ClusterPermission.java | 6 ------ .../client/v1/rbac/model/CollectionsPermission.java | 6 ------ .../io/weaviate/client/v1/rbac/model/DataPermission.java | 6 ------ .../io/weaviate/client/v1/rbac/model/NodesPermission.java | 6 ------ .../java/io/weaviate/client/v1/rbac/model/Permission.java | 4 +++- .../io/weaviate/client/v1/rbac/model/RolesPermission.java | 6 ------ .../io/weaviate/client/v1/rbac/model/TenantsPermission.java | 6 ------ .../io/weaviate/client/v1/rbac/model/UsersPermission.java | 6 ------ 9 files changed, 3 insertions(+), 49 deletions(-) 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 index a22fd4c15..ab5cd4005 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -1,6 +1,5 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -19,11 +18,6 @@ public BackupsPermission(String collection, Action action) { this(collection, RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { MANAGE("manage_backups"); 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 index 80899341e..368a28294 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -1,6 +1,5 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -16,11 +15,6 @@ public ClusterPermission(Action action) { this(RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action); - } - @AllArgsConstructor public enum Action implements RbacAction { READ("read_cluster"); 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 index 74112ef60..c12633a0b 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -1,6 +1,5 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -19,11 +18,6 @@ public CollectionsPermission(String collection, Action action) { this(collection, RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { CREATE("create_collections"), 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 index c1e839508..b0fefc53a 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -1,6 +1,5 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -19,11 +18,6 @@ public DataPermission(String collection, Action action) { this(collection, RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { CREATE("create_data"), 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 index ff88913ed..3df60a0bc 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.SerializedName; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -39,11 +38,6 @@ public NodesPermission(String collection, Action action) { this.verbosity = verbosity; } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { READ("read_nodes"); 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 index 27da9aba4..e11e02839 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -15,7 +15,9 @@ public abstract class Permission

> { } /** Convert the permission to {@link WeaviatePermission}. */ - public abstract WeaviatePermission toWeaviate(); + public WeaviatePermission toWeaviate() { + return new WeaviatePermission(this.action, this); + }; /** * Convert {@link WeaviatePermission} to concrete {@link Permission}. 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 index ef671dfe3..0a2f26585 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.SerializedName; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -27,11 +26,6 @@ public RolesPermission(String role, Scope scope, Action action) { this(role, scope, RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { CREATE("create_roles"), 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 index efc8711b5..f8561b952 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -1,6 +1,5 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -17,11 +16,6 @@ public TenantsPermission(Action action) { this(RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); - } - @AllArgsConstructor public enum Action implements RbacAction { CREATE("create_tenants"), 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 index 8f288fc9c..95ffa997d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -1,7 +1,6 @@ package io.weaviate.client.v1.rbac.model; -import io.weaviate.client.v1.rbac.api.WeaviatePermission; import lombok.AllArgsConstructor; import lombok.Getter; @@ -14,11 +13,6 @@ public UsersPermission(Action action) { this(RbacAction.fromString(Action.class, action)); } - @Override - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action); - } - @AllArgsConstructor public enum Action implements RbacAction { READ("read_users"), From 5ab0636dac20addfdbd3343f9f1ac2511257b20b Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 26 Feb 2025 15:39:00 +0100 Subject: [PATCH 37/40] refactor: group permissions by resource Permission holds all actions it permits and is distinguished from other permissions by its resource filters. Permissions are "flattened" before being sent to Weaviate and then grouped / merged back together after the response's been deserialized. --- .../v1/async/rbac/api/PermissionChecker.java | 2 +- .../client/v1/rbac/api/PermissionAdder.java | 10 -- .../client/v1/rbac/api/PermissionChecker.java | 2 +- .../client/v1/rbac/api/PermissionRemover.java | 9 -- .../client/v1/rbac/api/RoleCreator.java | 9 -- .../v1/rbac/api/WeaviatePermission.java | 16 +- .../client/v1/rbac/api/WeaviateRole.java | 13 +- .../v1/rbac/model/BackupsPermission.java | 4 +- .../v1/rbac/model/ClusterPermission.java | 4 +- .../v1/rbac/model/CollectionsPermission.java | 4 +- .../client/v1/rbac/model/DataPermission.java | 4 +- .../client/v1/rbac/model/NodesPermission.java | 12 +- .../client/v1/rbac/model/Permission.java | 145 +++++++++++------- .../weaviate/client/v1/rbac/model/Role.java | 2 +- .../client/v1/rbac/model/RolesPermission.java | 8 +- .../v1/rbac/model/TenantsPermission.java | 4 +- .../client/v1/rbac/model/UsersPermission.java | 4 +- .../v1/rbac/api/WeaviatePermissionTest.java | 101 ++++++++++++ .../client/v1/rbac/model/PermissionTest.java | 15 +- .../client/async/rbac/ClientRbacTest.java | 15 -- .../client/rbac/ClientRbacTest.java | 15 -- .../tests/rbac/ClientRbacTestSuite.java | 34 ++-- 22 files changed, 265 insertions(+), 167 deletions(-) create mode 100644 src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java diff --git a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java index 5ae0a21cd..ae5b0950b 100644 --- a/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java +++ b/src/main/java/io/weaviate/client/v1/async/rbac/api/PermissionChecker.java @@ -32,7 +32,7 @@ public PermissionChecker withPermission(Permission permission) { @Override public Future> run(FutureCallback> callback) { - return sendPostRequest(path(), permission.toWeaviate(), Boolean.class, callback); + return sendPostRequest(path(), permission.firstToWeaviate(), Boolean.class, callback); } private String path() { 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 index 55ea08949..289687714 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java @@ -7,7 +7,6 @@ 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.Permission; @@ -31,15 +30,6 @@ public PermissionAdder withPermissions(Permission... permissions) { return this; } - public PermissionAdder withPermissions(Permission[]... permissions) { - List> all = new ArrayList<>(); - for (Permission[] perm : permissions) { - all.addAll(Arrays.asList(perm)); - } - this.permissions = all; - return this; - } - @AllArgsConstructor private static class Body { public final List permissions; 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 index 330e1e96b..8c6b05190 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java @@ -27,7 +27,7 @@ public PermissionChecker withPermission(Permission permission) { @Override public Result run() { - return new Result(sendPostRequest(path(), permission.toWeaviate(), Boolean.class)); + return new Result(sendPostRequest(path(), permission.firstToWeaviate(), Boolean.class)); } private String path() { 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 index 623f1d7fd..0f04a9d12 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java @@ -30,15 +30,6 @@ public PermissionRemover withPermissions(Permission... permissions) { return this; } - public PermissionRemover withPermissions(Permission[]... permissions) { - List> all = new ArrayList<>(); - for (Permission[] perm : permissions) { - all.addAll(Arrays.asList(perm)); - } - this.permissions = all; - return this; - } - @AllArgsConstructor private static class Body { public final List permissions; 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 index 01e6901ee..95c7d7e1c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java @@ -29,15 +29,6 @@ public RoleCreator withPermissions(Permission... permissions) { return this; } - public RoleCreator withPermissions(Permission[]... permissions) { - List> all = new ArrayList<>(); - for (Permission[] perm : permissions) { - all.addAll(Arrays.asList(perm)); - } - this.permissions = all; - return this; - } - @Override public Result run() { WeaviateRole role = new WeaviateRole(this.name, this.permissions); 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 index 40c0545ac..976f1da91 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java @@ -1,7 +1,7 @@ package io.weaviate.client.v1.rbac.api; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import io.weaviate.client.v1.rbac.model.BackupsPermission; import io.weaviate.client.v1.rbac.model.CollectionsPermission; @@ -10,14 +10,19 @@ 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 io.weaviate.client.v1.rbac.model.UsersPermission; import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; /** API model for serializing/deserializing permissions. */ @Getter @Builder @AllArgsConstructor +@EqualsAndHashCode +@ToString public class WeaviatePermission { String action; BackupsPermission backups; @@ -26,6 +31,7 @@ public class WeaviatePermission { NodesPermission nodes; RolesPermission roles; TenantsPermission tenants; + UsersPermission users; public WeaviatePermission(String action) { this.action = action; @@ -45,10 +51,16 @@ public

> WeaviatePermission(String action, Permission

this.roles = (RolesPermission) perm; } else if (perm instanceof TenantsPermission) { this.tenants = (TenantsPermission) perm; + } else if (perm instanceof UsersPermission) { + this.users = (UsersPermission) perm; } } public static List mergePermissions(List> permissions) { - return permissions.stream().map(perm -> perm.toWeaviate()).collect(Collectors.toList()); + List merged = new ArrayList<>(); + for (Permission perm : permissions) { + merged.addAll(perm.toWeaviate()); + } + return merged; } } 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 index 8e7080a4f..11b6d8ecb 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -1,5 +1,6 @@ package io.weaviate.client.v1.rbac.api; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -18,10 +19,18 @@ public WeaviateRole(String name, List> permissions) { this.permissions = WeaviatePermission.mergePermissions(permissions); } + /** Exposed for testing. */ + WeaviateRole(String name, WeaviatePermission... permissions) { + this.name = name; + this.permissions = Arrays.asList(permissions); + + } + /** Create {@link Role} from the API response object. */ public Role toRole() { List> permissions = this.permissions.stream() - .>map(perm -> Permission.fromWeaviate(perm)).collect(Collectors.toList()); - return new Role(this.name, permissions); + .>map(perm -> Permission.fromWeaviate(perm)) + .collect(Collectors.toList()); + return new Role(this.name, Permission.merge(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 index ab5cd4005..72016f22f 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java @@ -9,8 +9,8 @@ public class BackupsPermission extends Permission { final String collection; - public BackupsPermission(String collection, Action action) { - super(action); + public BackupsPermission(String collection, Action... actions) { + super(actions); this.collection = collection; } 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 index 368a28294..312fb4b8d 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java @@ -7,8 +7,8 @@ @Getter @EqualsAndHashCode(callSuper = true) public class ClusterPermission extends Permission { - public ClusterPermission(Action action) { - super(action); + public ClusterPermission(Action... actions) { + super(actions); } ClusterPermission(String action) { 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 index c12633a0b..e418bfba9 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java @@ -9,8 +9,8 @@ public class CollectionsPermission extends Permission { final String collection; - public CollectionsPermission(String collection, Action action) { - super(action); + public CollectionsPermission(String collection, Action... actions) { + super(actions); this.collection = collection; } 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 index b0fefc53a..8b952b6b6 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java @@ -9,8 +9,8 @@ public class DataPermission extends Permission { final String collection; - public DataPermission(String collection, Action action) { - super(action); + public DataPermission(String collection, Action... actions) { + super(actions); this.collection = collection; } 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 index 3df60a0bc..e0da98ca1 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java @@ -13,15 +13,15 @@ public class NodesPermission extends Permission { final Verbosity verbosity; /** Create permission scoped to all collections. */ - public NodesPermission(Verbosity verbosity, Action action) { - this("*", verbosity, action); + public NodesPermission(Verbosity verbosity, Action... actions) { + this("*", verbosity, actions); } /** * Permission scoped to a collection with {@link Verbosity#VERBOSE}. */ - public NodesPermission(String collection, Action action) { - this(collection, Verbosity.VERBOSE, action); + public NodesPermission(String collection, Action... actions) { + this(collection, Verbosity.VERBOSE, actions); } NodesPermission(Verbosity verbosity, String action) { @@ -32,8 +32,8 @@ public NodesPermission(String collection, Action action) { this(collection, verbosity, RbacAction.fromString(Action.class, action)); } - NodesPermission(String collection, Verbosity verbosity, Action action) { - super(action); + NodesPermission(String collection, Verbosity verbosity, Action... actions) { + super(actions); this.collection = collection; this.verbosity = verbosity; } 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 index e11e02839..9565739fb 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -1,24 +1,52 @@ package io.weaviate.client.v1.rbac.model; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + import io.weaviate.client.v1.rbac.api.WeaviatePermission; import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; @EqualsAndHashCode +@ToString public abstract class Permission

> { @Getter - final transient String action; + final transient List actions = new ArrayList<>(); - Permission(RbacAction action) { - this.action = action.getValue(); + Permission(RbacAction... actions) { + this.actions.addAll( + Arrays.stream(actions) + .map(RbacAction::getValue) + .collect(Collectors.toList())); } - /** Convert the permission to {@link WeaviatePermission}. */ - public WeaviatePermission toWeaviate() { - return new WeaviatePermission(this.action, this); + /** Convert the permission to a list of {@link WeaviatePermission}. */ + public WeaviatePermission firstToWeaviate() { + if (actions.isEmpty()) { + return null; + } + return this.toWeaviate(actions.get(0)); }; + public List toWeaviate() { + return this.actions.stream().map(this::toWeaviate).collect(Collectors.toList()); + } + + private WeaviatePermission toWeaviate(String action) { + return new WeaviatePermission(action, this); + } + /** * Convert {@link WeaviatePermission} to concrete {@link Permission}. */ @@ -49,15 +77,40 @@ public static Permission fromWeaviate(WeaviatePermission perm) { return null; } + public static final List> merge(List> permissions) { + @RequiredArgsConstructor + @EqualsAndHashCode + class Key { + final int hash; + final Class cls; + } + + Map> result = new LinkedHashMap<>(); // preserve insertion order + for (Permission perm : permissions) { + int hash = HashCodeBuilder.reflectionHashCode(perm, "actions"); + Key key = new Key(hash, perm.getClass()); + Permission stored = result.putIfAbsent(key, perm); + if (stored != null) { + stored.actions.addAll(perm.actions); + } + } + return result.values().stream().>map(perm -> { + Set actions = new HashSet<>(perm.actions); + perm.actions.clear(); + perm.actions.addAll(actions); + return perm; + }).collect(Collectors.toList()); + } + /** * Create {@link BackupsPermission} for a collection. *

* Example: * {@code Permission.backups(BackupsPermission.Action.MANAGE, "Pizza") } */ - public static BackupsPermission[] backups(String collection, BackupsPermission.Action action) { - checkDeprecation(action); - return new BackupsPermission[] { new BackupsPermission(collection, action) }; + public static BackupsPermission backups(String collection, BackupsPermission.Action... actions) { + checkDeprecation(actions); + return new BackupsPermission(collection, actions); } /** @@ -65,9 +118,9 @@ public static BackupsPermission[] backups(String collection, BackupsPermission.A *

* Example: {@code Permission.cluster(ClusterPermission.Action.READ, "Pizza") } */ - public static ClusterPermission[] cluster(ClusterPermission.Action action) { - checkDeprecation(action); - return new ClusterPermission[] { new ClusterPermission(action) }; + public static ClusterPermission cluster(ClusterPermission.Action... actions) { + checkDeprecation(actions); + return new ClusterPermission(actions); } /** @@ -76,13 +129,9 @@ public static ClusterPermission[] cluster(ClusterPermission.Action action) { * Example: * {@code Permission.collections("Pizza", CollectionsPermission.Action.READ, CollectionsPermission.Action.UPDATE) } */ - public static CollectionsPermission[] collections(String collection, CollectionsPermission.Action... actions) { - CollectionsPermission[] permissions = new CollectionsPermission[actions.length]; - for (int i = 0; i < actions.length; i++) { - checkDeprecation(actions[i]); - permissions[i] = new CollectionsPermission(collection, actions[i]); - } - return permissions; + public static CollectionsPermission collections(String collection, CollectionsPermission.Action... actions) { + checkDeprecation(actions); + return new CollectionsPermission(collection, actions); } /** @@ -92,13 +141,9 @@ public static CollectionsPermission[] collections(String collection, Collections * Example: * {@code Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.UPDATE) } */ - public static DataPermission[] data(String collection, DataPermission.Action... actions) { - DataPermission[] permissions = new DataPermission[actions.length]; - for (int i = 0; i < actions.length; i++) { - checkDeprecation(actions[i]); - permissions[i] = new DataPermission(collection, actions[i]); - } - return permissions; + public static DataPermission data(String collection, DataPermission.Action... actions) { + checkDeprecation(actions); + return new DataPermission(collection, actions); } /** @@ -107,8 +152,9 @@ public static DataPermission[] data(String collection, DataPermission.Action... * Example: * {@code Permission.nodes(NodesPermission.Verbosity.MINIMAL, NodesPermission.Action.READ) } */ - public static NodesPermission[] nodes(NodesPermission.Verbosity verbosity, NodesPermission.Action action) { - return new NodesPermission[] { new NodesPermission(verbosity, action) }; + public static NodesPermission nodes(NodesPermission.Verbosity verbosity, NodesPermission.Action... actions) { + checkDeprecation(actions); + return new NodesPermission(verbosity, actions); } /** @@ -118,9 +164,9 @@ public static NodesPermission[] nodes(NodesPermission.Verbosity verbosity, Nodes * Example: * {@code Permission.nodes("Pizza", NodesPermission.Action.READ) } */ - public static NodesPermission[] nodes(String collection, NodesPermission.Action action) { - checkDeprecation(action); - return new NodesPermission[] { new NodesPermission(collection, action) }; + public static NodesPermission nodes(String collection, NodesPermission.Action... actions) { + checkDeprecation(actions); + return new NodesPermission(collection, actions); } /** @@ -129,13 +175,9 @@ public static NodesPermission[] nodes(String collection, NodesPermission.Action * Example: * {@code Permission.roles("MyRole", RolesPermission.Action.READ, RolesPermission.Action.UPDATE) } */ - public static RolesPermission[] roles(String role, RolesPermission.Action... actions) { - RolesPermission[] permissions = new RolesPermission[actions.length]; - for (int i = 0; i < actions.length; i++) { - checkDeprecation(actions[i]); - permissions[i] = new RolesPermission(role, actions[i]); - } - return permissions; + public static RolesPermission roles(String role, RolesPermission.Action... actions) { + checkDeprecation(actions); + return new RolesPermission(role, actions); } /** @@ -144,9 +186,9 @@ public static RolesPermission[] roles(String role, RolesPermission.Action... act * Example: * {@code Permission.tenants(TenantsPermission.Action.READ) } */ - public static TenantsPermission[] tenants(TenantsPermission.Action action) { - checkDeprecation(action); - return new TenantsPermission[] { new TenantsPermission(action) }; + public static TenantsPermission tenants(TenantsPermission.Action... actions) { + checkDeprecation(actions); + return new TenantsPermission(actions); } /** @@ -155,20 +197,17 @@ public static TenantsPermission[] tenants(TenantsPermission.Action action) { * Example: * {@code Permission.users(UsersPermission.Action.READ) } */ - public static UsersPermission[] users(UsersPermission.Action action) { - checkDeprecation(action); - return new UsersPermission[] { new UsersPermission(action) }; + public static UsersPermission users(UsersPermission.Action... actions) { + checkDeprecation(actions); + return new UsersPermission(actions); } - private static void checkDeprecation(RbacAction action) throws IllegalArgumentException { - if (action.isDeprecated()) { - throw new IllegalArgumentException(action.getValue() - + " is hard-deprecated and should only be used to read legacy permissions created in v1.28"); + private static void checkDeprecation(RbacAction... actions) throws IllegalArgumentException { + for (RbacAction action : actions) { + if (action.isDeprecated()) { + throw new IllegalArgumentException(action.getValue() + + " is hard-deprecated and should only be used to read legacy permissions created in v1.28"); + } } } - - public String toString() { - return String.format("Permission", this.action); - } - } 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 index ac0ef33d8..52c9c8bec 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Role.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java @@ -20,7 +20,7 @@ public String toString() { "Role", this.name, permissions.isEmpty() ? "none" - : String.join(", ", permissions.stream().map(Permission::getAction) + : String.join(",\n", permissions.stream().map(Permission::toString) .collect(Collectors.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 index 0a2f26585..dd2e3fbb0 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java @@ -12,12 +12,12 @@ public class RolesPermission extends Permission { final String role; final Scope scope; - public RolesPermission(String role, Action action) { - this(role, null, action); + public RolesPermission(String role, Action... actions) { + this(role, null, actions); } - public RolesPermission(String role, Scope scope, Action action) { - super(action); + public RolesPermission(String role, Scope scope, Action... actions) { + super(actions); this.role = role; this.scope = scope; } 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 index f8561b952..2229b36d3 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java @@ -8,8 +8,8 @@ @EqualsAndHashCode(callSuper = true) public class TenantsPermission extends Permission { - public TenantsPermission(Action action) { - super(action); + public TenantsPermission(Action... actions) { + super(actions); } TenantsPermission(String action) { 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 index 95ffa997d..f77bd516a 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java @@ -5,8 +5,8 @@ import lombok.Getter; public class UsersPermission extends Permission { - public UsersPermission(Action action) { - super(action); + public UsersPermission(Action... actions) { + super(actions); } UsersPermission(String action) { diff --git a/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java b/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java new file mode 100644 index 000000000..12c513880 --- /dev/null +++ b/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java @@ -0,0 +1,101 @@ +package io.weaviate.client.v1.rbac.api; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.Test; + +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.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.client.v1.rbac.model.UsersPermission; + +public class WeaviatePermissionTest { + /** + * When serialized to the API request body, permissions must be "flattened", + * i.e. a single action per permission. When the response is deserialised, + * permissions with for the same resource should be grouped together. + */ + @Test + public void testMergedPermissions() { + WeaviatePermission[] apiPermissions = { + // Manage Pizza backups + new WeaviatePermission("manage_backups", new BackupsPermission("Pizza")), + + // Manage and read Pizza data + new WeaviatePermission("manage_data", new DataPermission("Pizza")), + new WeaviatePermission("read_data", new DataPermission("Pizza")), + + // Update and delete Songs data + new WeaviatePermission("update_data", new DataPermission("Songs")), + new WeaviatePermission("delete_data", new DataPermission("Songs")), + + // Read nodes with Pizza collection + new WeaviatePermission("read_nodes", new NodesPermission("Pizza")), + + // Read nodes for any collection with verbosity="verbose" + new WeaviatePermission("read_nodes", new NodesPermission(NodesPermission.Verbosity.VERBOSE)), + + // Read Reader role + new WeaviatePermission("read_roles", new RolesPermission("Reader")), + + // Create and update CreatorUpdater role + new WeaviatePermission("create_roles", new RolesPermission("CreatorUpdater", RolesPermission.Scope.ALL)), + new WeaviatePermission("update_roles", new RolesPermission("CreatorUpdater", RolesPermission.Scope.ALL)), + + // Delete and update Pizza collection definition + new WeaviatePermission("delete_collections", new CollectionsPermission("Pizza")), + new WeaviatePermission("update_collections", new CollectionsPermission("Pizza")), + + // Read Songs collection definition + new WeaviatePermission("read_collections", new CollectionsPermission("Songs")), + + // Read clusters + new WeaviatePermission("read_cluster", new ClusterPermission()), + + // Create and update tenants + new WeaviatePermission("create_tenants", new TenantsPermission()), + new WeaviatePermission("update_tenants", new TenantsPermission()), + + // Read and delete users + new WeaviatePermission("read_users", new UsersPermission()), + new WeaviatePermission("assign_and_revoke_users", new UsersPermission()), + }; + + Permission[] libraryPermissions = { + new BackupsPermission("Pizza", BackupsPermission.Action.MANAGE), + new DataPermission("Pizza", DataPermission.Action.MANAGE, DataPermission.Action.READ), + new DataPermission("Songs", DataPermission.Action.UPDATE, DataPermission.Action.DELETE), + new NodesPermission("Pizza", NodesPermission.Action.READ), + new NodesPermission(NodesPermission.Verbosity.VERBOSE, NodesPermission.Action.READ), + new RolesPermission("Reader", RolesPermission.Action.READ), + new RolesPermission("CreatorUpdater", RolesPermission.Scope.ALL, + RolesPermission.Action.CREATE, RolesPermission.Action.UPDATE), + new CollectionsPermission("Pizza", CollectionsPermission.Action.DELETE, CollectionsPermission.Action.UPDATE), + new CollectionsPermission("Songs", CollectionsPermission.Action.READ), + new ClusterPermission(ClusterPermission.Action.READ), + new TenantsPermission(TenantsPermission.Action.CREATE, TenantsPermission.Action.UPDATE), + new UsersPermission(UsersPermission.Action.READ, UsersPermission.Action.ASSIGN_AND_REVOKE), + }; + + // { + // WeaviateRole role = new WeaviateRole("TestRole", + // Arrays.asList(libraryPermissions)); + // WeaviatePermission[] got = role.getPermissions().toArray(new + // WeaviatePermission[] {}); + // assertArrayEquals(apiPermissions, got, "lib -> api conversion"); + // } + + { + WeaviateRole role = new WeaviateRole("TestRole", apiPermissions); + Role libRole = role.toRole(); + Permission[] got = libRole.permissions.toArray(new Permission[] {}); + assertArrayEquals(libraryPermissions, got, "api -> lib conversion"); + } + } +} 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 index 702cc04d2..1d1f3689e 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.util.ArrayList; -import java.util.Arrays; import java.util.function.Supplier; import org.junit.Test; @@ -17,6 +16,7 @@ import com.jparams.junit4.JParamsTestRunner; import com.jparams.junit4.data.DataMethod; +import com.jparams.junit4.description.Name; import io.weaviate.client.v1.rbac.api.WeaviatePermission; @@ -71,17 +71,18 @@ public static Object[][] serializationTestCases() { { "users permission", (Supplier>) () -> users, - new WeaviatePermission("read_users"), + new WeaviatePermission("read_users", users), }, }; } @DataMethod(source = PermissionTest.class, method = "serializationTestCases") + @Name("{0}") @Test - public void testToWeaviate(String name, Supplier> permFunc, WeaviatePermission expected) + public void testFirstToWeaviate(String name, Supplier> permFunc, WeaviatePermission expected) throws Exception { Permission perm = permFunc.get(); - MatcherAssert.assertThat(name, perm.toWeaviate(), sameAs(expected)); + MatcherAssert.assertThat(name, perm.firstToWeaviate(), sameAs(expected)); } private static Matcher sameAs(T expected) { @@ -103,6 +104,7 @@ public void testDefaultRolesPermission() { } @DataMethod(source = PermissionTest.class, method = "serializationTestCases") + @Name("{0}") @Test public void testFromWeaviate(String name, Supplier> expectedFunc, WeaviatePermission input) @@ -156,9 +158,10 @@ public static Object[][] groupedConstructors() { } @DataMethod(source = PermissionTest.class, method = "groupedConstructors") + @Name("{0}") @Test - public void testGroupedConstructors(Permission>[] permissions, String[] expectedActions) { - Object[] actualActions = Arrays.stream(permissions).map(Permission::getAction).toArray(); + public void testGroupedConstructors(Permission> permission, String[] expectedActions) { + Object[] actualActions = permission.getActions().toArray(); assertArrayEquals(expectedActions, actualActions, "set of allowed actions do not match"); } diff --git a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java index 75185f2ba..f854907e2 100644 --- a/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/async/rbac/ClientRbacTest.java @@ -64,11 +64,6 @@ public Result createRole(String role, Permission... permissions) { return rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); } - @Override - public Result createRole(String role, Permission[]... permissions) { - return rethrow(() -> roles.creator().withName(role).withPermissions(permissions).run()); - } - @Override public void deleteRole(String role) { rethrow(() -> roles.deleter().withName(role).run()); @@ -89,18 +84,8 @@ public Result addPermissions(String role, Permission... permissions) { return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run()); } - @Override - public Result addPermissions(String role, Permission[]... permissions) { - return rethrow(() -> roles.permissionAdder().withRole(role).withPermissions(permissions).run()); - } - @Override public Result removePermissions(String role, Permission... permissions) { return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run()); } - - @Override - public Result removePermissions(String role, Permission[]... permissions) { - return rethrow(() -> roles.permissionRemover().withRole(role).withPermissions(permissions).run()); - } } diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java index 3922e88f0..861c44a43 100644 --- a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java +++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java @@ -42,11 +42,6 @@ public Result createRole(String role, Permission... permissions) { return roles.creator().withName(role).withPermissions(permissions).run(); } - @Override - public Result createRole(String role, Permission[]... permissions) { - return roles.creator().withName(role).withPermissions(permissions).run(); - } - @Override public void deleteRole(String role) { roles.deleter().withName(role).run(); @@ -67,18 +62,8 @@ public Result addPermissions(String role, Permission... permissions) { return roles.permissionAdder().withRole(role).withPermissions(permissions).run(); } - @Override - public Result addPermissions(String role, Permission[]... permissions) { - return roles.permissionAdder().withRole(role).withPermissions(permissions).run(); - } - @Override public Result removePermissions(String role, Permission... permissions) { return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); } - - @Override - public Result removePermissions(String role, Permission[]... permissions) { - return roles.permissionRemover().withRole(role).withPermissions(permissions).run(); - } } diff --git a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java index a3ad64420..a0570e00c 100644 --- a/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java +++ b/src/test/java/io/weaviate/integration/tests/rbac/ClientRbacTestSuite.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.Arrays; import java.util.List; import java.util.function.Supplier; @@ -113,7 +114,7 @@ public void testCreate(String _name, Supplier rbac) { String myRole = roleName("VectorOwner"); String myCollection = "Pizza"; - Permission[][] wantPermissions = new Permission[][] { + Permission[] wantPermissions = new Permission[] { Permission.backups(myCollection, BackupsPermission.Action.MANAGE), Permission.cluster(ClusterPermission.Action.READ), Permission.nodes(myCollection, NodesPermission.Action.READ), @@ -137,10 +138,9 @@ public void testCreate(String _name, Supplier rbac) { 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][0]; // We create each permission group with only 1 action + Arrays.stream(wantPermissions).forEach(perm -> { assertTrue("should have permission " + perm, checkHasPermission(roles, myRole, perm)); - } + }); } finally { roles.deleteRole(myRole); } @@ -156,7 +156,7 @@ public void testCreate(String _name, Supplier rbac) { public void testAddPermissions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission toAdd = Permission.cluster(ClusterPermission.Action.READ)[0]; + Permission toAdd = Permission.cluster(ClusterPermission.Action.READ); try { // Arrange roles.createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE)); @@ -183,7 +183,7 @@ public void testAddPermissions(String _name, Supplier rbac) { public void testAddPermissionsMultipleActions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission[] toAdd = Permission.data("Pizza", + Permission toAdd = Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.CREATE); try { @@ -197,10 +197,10 @@ public void testAddPermissionsMultipleActions(String _name, Supplier rbac) assertNull("add-permissions operation error", response.getError()); // Assert - for (Permission perm : toAdd) { - assertTrue("should have permission " + perm, checkHasPermission(roles, myRole, perm)); - } - } finally { + assertTrue("should have permission " + toAdd, checkHasPermission(roles, myRole, toAdd)); + } finally + + { roles.deleteRole(myRole); } } @@ -216,7 +216,7 @@ public void testAddPermissionsMultipleActions(String _name, Supplier rbac) public void testRemovePermissions(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE)[0]; + Permission toRemove = Permission.tenants(TenantsPermission.Action.DELETE); try { // Arrange roles.createRole(myRole, @@ -247,7 +247,7 @@ public void testRemovePermissions(String _name, Supplier rbac) { public void testRemovePermissionsMultipleAction(String _name, Supplier rbac) { Rbac roles = rbac.get(); String myRole = roleName("VectorOwner"); - Permission[] toRemove = Permission.data("Pizza", + Permission toRemove = Permission.data("Pizza", DataPermission.Action.READ, DataPermission.Action.CREATE); try { @@ -265,9 +265,7 @@ public void testRemovePermissionsMultipleAction(String _name, Supplier rba assertNull("remove-permissions operation error", response.getError()); // Assert - for (Permission perm : toRemove) { - assertFalse("should not have permission " + toRemove, checkHasPermission(roles, myRole, perm)); - } + assertFalse("should not have permission " + toRemove, checkHasPermission(roles, myRole, toRemove)); } finally { roles.deleteRole(myRole); } @@ -296,8 +294,6 @@ public interface Rbac { Result createRole(String role, Permission... permissions); - Result createRole(String role, Permission[]... permissions); - void deleteRole(String role); Result hasPermission(String role, Permission perm); @@ -306,11 +302,7 @@ public interface Rbac { Result addPermissions(String role, Permission... permissions); - Result addPermissions(String role, Permission[]... permissions); - Result removePermissions(String role, Permission... permissions); - Result removePermissions(String role, Permission[]... permissions); } - } From d43e0d023af6016dd05a573ccb9c3f000b1539dd Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 26 Feb 2025 22:40:16 +0100 Subject: [PATCH 38/40] refactor: store actions in a Set This lets us skip deduplication in merge() --- .../client/v1/rbac/model/Permission.java | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) 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 index 9565739fb..c58b07065 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -1,9 +1,8 @@ package io.weaviate.client.v1.rbac.model; -import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -14,15 +13,23 @@ import io.weaviate.client.v1.rbac.api.WeaviatePermission; import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.ToString; @EqualsAndHashCode @ToString public abstract class Permission

> { - @Getter - final transient List actions = new ArrayList<>(); + /** + * Actions allowed by this permission. Transience allows easily + * serializing "action" separately from other attributes in the + * extending permission types. + * + * LinkedHashSet preserves insertion order for predictability. + */ + final transient Set actions = new LinkedHashSet<>(); + + public List getActions() { + return actions.stream().collect(Collectors.toList()); + } Permission(RbacAction... actions) { this.actions.addAll( @@ -36,7 +43,7 @@ public WeaviatePermission firstToWeaviate() { if (actions.isEmpty()) { return null; } - return this.toWeaviate(actions.get(0)); + return this.toWeaviate(actions.iterator().next()); }; public List toWeaviate() { @@ -77,29 +84,39 @@ public static Permission fromWeaviate(WeaviatePermission perm) { return null; } + /** + * Merge permissions by their type and targeted resource. Weaviate server + * returns separate entries for each action, but working with a + * permission-per-resource model is more convenient. + * + *

+ * Example: convert Data[read_data, MyCollection], Data[delete_data, + * MyCollection] => Data[[read_data, delete_data], MyCollection]. + */ public static final List> merge(List> permissions) { - @RequiredArgsConstructor @EqualsAndHashCode class Key { + // hash is computed on all permission fields apart from "actions" which + // is what we need to aggregate. final int hash; + // Permission types which do not have any filters differentiate by their class. final Class cls; + + private Key(Object object) { + this.hash = HashCodeBuilder.reflectionHashCode(object, "actions"); + this.cls = object.getClass(); + } } Map> result = new LinkedHashMap<>(); // preserve insertion order for (Permission perm : permissions) { - int hash = HashCodeBuilder.reflectionHashCode(perm, "actions"); - Key key = new Key(hash, perm.getClass()); + Key key = new Key(perm); Permission stored = result.putIfAbsent(key, perm); - if (stored != null) { + if (stored != null) { // A permission for this key already exists, add all actions. stored.actions.addAll(perm.actions); } } - return result.values().stream().>map(perm -> { - Set actions = new HashSet<>(perm.actions); - perm.actions.clear(); - perm.actions.addAll(actions); - return perm; - }).collect(Collectors.toList()); + return result.values().stream().collect(Collectors.toList()); } /** From 4cc3d1e4cf548c35134ae8a58fc9fcbf852cf2ac Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Wed, 26 Feb 2025 22:49:10 +0100 Subject: [PATCH 39/40] doc: clarify usage for firstToWeaviate() --- .../io/weaviate/client/v1/rbac/model/Permission.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 index c58b07065..6862ea61c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java +++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java @@ -10,6 +10,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; +import io.weaviate.client.v1.async.rbac.api.PermissionChecker; import io.weaviate.client.v1.rbac.api.WeaviatePermission; import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity; import lombok.EqualsAndHashCode; @@ -38,7 +39,12 @@ public List getActions() { .collect(Collectors.toList())); } - /** Convert the permission to a list of {@link WeaviatePermission}. */ + /** + * Create {@link WeaviatePermission} with the first action in the actions list. + * + * This is meant to be used with {@link PermissionChecker}, which can only + * include a permission with a single action in the request. + */ public WeaviatePermission firstToWeaviate() { if (actions.isEmpty()) { return null; @@ -46,6 +52,7 @@ public WeaviatePermission firstToWeaviate() { return this.toWeaviate(actions.iterator().next()); }; + /** Convert the permission to a list of {@link WeaviatePermission}. */ public List toWeaviate() { return this.actions.stream().map(this::toWeaviate).collect(Collectors.toList()); } From 74a00b87e0fa736bee4559aba3b528ecf7103454 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Thu, 27 Feb 2025 13:31:21 +0100 Subject: [PATCH 40/40] test: re-activate commented out test case --- .../weaviate/client/v1/rbac/api/WeaviateRole.java | 1 - .../v1/rbac/api/WeaviatePermissionTest.java | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) 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 index 11b6d8ecb..ffc4d9b3c 100644 --- a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java +++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java @@ -23,7 +23,6 @@ public WeaviateRole(String name, List> permissions) { WeaviateRole(String name, WeaviatePermission... permissions) { this.name = name; this.permissions = Arrays.asList(permissions); - } /** Create {@link Role} from the API response object. */ diff --git a/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java b/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java index 12c513880..8fc41d604 100644 --- a/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java +++ b/src/test/java/io/weaviate/client/v1/rbac/api/WeaviatePermissionTest.java @@ -2,6 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import java.util.Arrays; + import org.junit.Test; import io.weaviate.client.v1.rbac.model.BackupsPermission; @@ -83,13 +85,12 @@ public void testMergedPermissions() { new UsersPermission(UsersPermission.Action.READ, UsersPermission.Action.ASSIGN_AND_REVOKE), }; - // { - // WeaviateRole role = new WeaviateRole("TestRole", - // Arrays.asList(libraryPermissions)); - // WeaviatePermission[] got = role.getPermissions().toArray(new - // WeaviatePermission[] {}); - // assertArrayEquals(apiPermissions, got, "lib -> api conversion"); - // } + { + WeaviateRole role = new WeaviateRole("TestRole", + Arrays.asList(libraryPermissions)); + WeaviatePermission[] got = role.getPermissions().toArray(new WeaviatePermission[] {}); + assertArrayEquals(apiPermissions, got, "lib -> api conversion"); + } { WeaviateRole role = new WeaviateRole("TestRole", apiPermissions);