Skip to content
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
8575576
WIP on multiple sharable resource types per index
cwperks Sep 26, 2025
1663969
Fix compilation issues
cwperks Sep 26, 2025
c46cd2d
Make more generic collection
cwperks Sep 26, 2025
6e4b679
Fix unit tests
cwperks Sep 26, 2025
f37c54c
Make DocRequests specify type
cwperks Sep 26, 2025
14869cf
Few test fixes
cwperks Sep 26, 2025
9a66864
Fix Share API tests
cwperks Sep 26, 2025
4c83aa5
Fix tests
cwperks Sep 26, 2025
ebfd5bd
Makes resources settings dynamically updateable
DarshitChanpura Oct 2, 2025
32f903b
Allows plugin to be control codepath based on protected resource types
DarshitChanpura Oct 2, 2025
5f7beb4
Fix setting registration
DarshitChanpura Oct 2, 2025
696482e
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 2, 2025
c2988ea
Adds changelog entry
DarshitChanpura Oct 2, 2025
fbdae9f
Corrects usage of resource-sharing flag
DarshitChanpura Oct 2, 2025
a3493a6
Fix share action rest channel consumer
DarshitChanpura Oct 7, 2025
9dd02a1
Fix sample plugin tests
DarshitChanpura Oct 7, 2025
9930ae3
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 7, 2025
1a94f40
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 7, 2025
42b7230
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 8, 2025
b333d12
Registers extensions regardless of whether their resources are marked…
DarshitChanpura Oct 8, 2025
ea333b9
Adds tests for the new dynamic settings and update existing tests to …
DarshitChanpura Oct 8, 2025
7c2c896
Adds documentation
DarshitChanpura Oct 8, 2025
258229d
Merge branch 'main' into dynamic-resource-settings
DarshitChanpura Oct 9, 2025
13204e2
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 9, 2025
09b16eb
Merge remote-tracking branch 'upstream/main' into dynamic-resource-se…
DarshitChanpura Oct 13, 2025
8d186fe
Fixes resource search request evaluation
DarshitChanpura Oct 13, 2025
b707959
Adds caching to resource indices and makes read-write operations thre…
DarshitChanpura Oct 13, 2025
877bb11
Fixes type enabled check
DarshitChanpura Oct 13, 2025
f9fb61a
Refactors resource type fetch logic
DarshitChanpura Oct 13, 2025
0bd05b9
Rebase with main
cwperks Oct 13, 2025
1bc85b4
Merge branch 'dynamic-resource-settings' into multiple-resource-type
cwperks Oct 14, 2025
3a3cf8c
Merge with main
cwperks Oct 14, 2025
5a569d3
Merge branch 'main' into multiple-resource-type
cwperks Oct 14, 2025
f53bdfb
Back out unrelated changes
cwperks Oct 14, 2025
7df24dc
Clean up changes
cwperks Oct 14, 2025
4dc058a
Use correct method
cwperks Oct 14, 2025
17c0384
Merge branch 'main' into multiple-resource-type
cwperks Oct 15, 2025
eaefa32
Add actions around sample-resource-group
cwperks Oct 15, 2025
287bfef
Fix test
cwperks Oct 16, 2025
5b3f561
Handle case where index is passed explicitly
cwperks Oct 16, 2025
812c428
Refactor to use methods from AbstractApiIntegrationTest
cwperks Oct 16, 2025
19bbd61
Remove calls to awaitSharingEntry
cwperks Oct 16, 2025
bbe3fbe
Revert "Handle case where index is passed explicitly"
cwperks Oct 16, 2025
115e9d4
Merge branch 'main' into multiple-resource-type
cwperks Oct 16, 2025
4499615
Fix compilation errors
cwperks Oct 16, 2025
d487a01
Fix tests
cwperks Oct 16, 2025
86d8dad
Merge branch 'main' into multiple-resource-type
cwperks Oct 20, 2025
d3c22c0
Use Set
cwperks Oct 20, 2025
3d25b31
Address review feedback
cwperks Oct 20, 2025
06bb26f
Support multiple resource types in migrate api payload
cwperks Oct 20, 2025
67f3a2b
Fix tests
cwperks Oct 20, 2025
dab8bb0
Address review feedback
cwperks Oct 20, 2025
ba937a6
Adds changelog entry
DarshitChanpura Oct 20, 2025
4b3d8da
Merge remote-tracking branch 'upstream/main' into multiple-resource-type
DarshitChanpura Oct 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion RESOURCE_SHARING_AND_ACCESS_CONTROL.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ Read documents from a plugin’s index and migrate ownership and backend role-ba
| `source_index` | string | yes | Name of the plugin index containing the existing resource documents |
| `username_path` | string | yes | JSON Pointer to the username field inside each document |
| `backend_roles_path` | string | yes | JSON Pointer to the backend_roles field (must point to a JSON array) |
| `type_path` | string | no | JSON Pointer to the resource type field inside each document (required if multiple resource types in same resource index) |
| `default_access_level` | string | yes | Default access level to assign migrated backend_roles. Must be one from the available action-groups for this type. See `resource-action-groups.yml`. |

**Example Request**
Expand All @@ -619,7 +620,8 @@ Read documents from a plugin’s index and migrate ownership and backend role-ba
{
"source_index": ".sample_resource",
"username_path": "/owner",
"backend_roles_path": "/access/backend_roles",
"backend_roles_path": "/backend_roles",
"type_path": "/type",
"default_access_level": "read_only"
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.opensearch.sample.utils.Constants.RESOURCE_GROUP_TYPE;
import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;
import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX;
Expand Down Expand Up @@ -79,19 +80,29 @@ public final class TestUtils {
public static final String SAMPLE_READ_WRITE = "sample_read_write";
public static final String SAMPLE_FULL_ACCESS = "sample_full_access";

public static final String SAMPLE_GROUP_READ_ONLY = "sample_group_read_only";
public static final String SAMPLE_GROUP_READ_WRITE = "sample_group_read_write";
public static final String SAMPLE_GROUP_FULL_ACCESS = "sample_group_full_access";

public static final String SAMPLE_RESOURCE_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/create";
public static final String SAMPLE_RESOURCE_GET_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/get";
public static final String SAMPLE_RESOURCE_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/update";
public static final String SAMPLE_RESOURCE_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/delete";
public static final String SAMPLE_RESOURCE_SEARCH_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/search";

public static final String SAMPLE_RESOURCE_GROUP_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/group/create";
public static final String SAMPLE_RESOURCE_GROUP_GET_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/group/get";
public static final String SAMPLE_RESOURCE_GROUP_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/group/update";
public static final String SAMPLE_RESOURCE_GROUP_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/group/delete";
public static final String SAMPLE_RESOURCE_GROUP_SEARCH_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/group/search";

public static final String RESOURCE_SHARING_MIGRATION_ENDPOINT = "_plugins/_security/api/resources/migrate";
public static final String SECURITY_SHARE_ENDPOINT = "_plugins/_security/api/resource/share";
public static final String SECURITY_TYPES_ENDPOINT = "_plugins/_security/api/resource/types";
public static final String SECURITY_LIST_ENDPOINT = "_plugins/_security/api/resource/list";

public static LocalCluster newCluster(boolean featureEnabled, boolean systemIndexEnabled) {
return newCluster(featureEnabled, systemIndexEnabled, List.of(RESOURCE_TYPE));
return newCluster(featureEnabled, systemIndexEnabled, List.of(RESOURCE_TYPE, RESOURCE_GROUP_TYPE));
}

public static LocalCluster newCluster(boolean featureEnabled, boolean systemIndexEnabled, List<String> protectedResourceTypes) {
Expand Down Expand Up @@ -144,31 +155,37 @@ public static String directSharePayload(String resourceId, String creator, Strin
public static String migrationPayload_valid() {
return """
{
"source_index": "%s",
"username_path": "%s",
"backend_roles_path": "%s",
"default_access_level": "%s"
"source_index": "%s",
"username_path": "%s",
"backend_roles_path": "%s",
"default_access_level": {
"sample-resource": "%s"
}
}
""".formatted(RESOURCE_INDEX_NAME, "user/name", "user/backend_roles", "sample_read_only");
}

public static String migrationPayload_valid_withSpecifiedAccessLevel(String accessLevel) {
return """
{
"source_index": "%s",
"username_path": "%s",
"backend_roles_path": "%s",
"default_access_level": "%s"
"source_index": "%s",
"username_path": "%s",
"backend_roles_path": "%s",
"default_access_level": {
"sample-resource": "%s"
}
}
""".formatted(RESOURCE_INDEX_NAME, "user/name", "user/backend_roles", accessLevel);
""".formatted(RESOURCE_INDEX_NAME, "user/name", "user/backend_roles", accessLevel);
}

public static String migrationPayload_missingSourceIndex() {
return """
{
"username_path": "%s",
"backend_roles_path": "%s",
"default_access_level": "%s"
"default_access_level": {
"sample-resource": "%s"
}
}
""".formatted("user/name", "user/backend_roles", "sample_read_only");
}
Expand All @@ -178,7 +195,9 @@ public static String migrationPayload_missingUserName() {
{
"source_index": "%s",
"backend_roles_path": "%s",
"default_access_level": "%s"
"default_access_level": {
"sample-resource": "%s"
}
}
""".formatted(RESOURCE_INDEX_NAME, "user/backend_roles", "sample_read_only");
}
Expand All @@ -188,7 +207,9 @@ public static String migrationPayload_missingBackendRoles() {
{
"source_index": "%s",
"username_path": "%s",
"default_access_level": "%s"
"default_access_level": {
"sample-resource": "%s"
}
}
""".formatted(RESOURCE_INDEX_NAME, "user/name", "sample_read_only");
}
Expand Down Expand Up @@ -330,6 +351,15 @@ public String createSampleResourceAs(TestSecurityConfig.User user, Header... hea
}
}

public String createSampleResourceGroupAs(TestSecurityConfig.User user, Header... headers) {
try (TestRestClient client = cluster.getRestClient(user)) {
String sample = "{\"name\":\"samplegroup\"}";
TestRestClient.HttpResponse resp = client.putJson(SAMPLE_RESOURCE_GROUP_CREATE_ENDPOINT, sample, headers);
resp.assertStatusCode(HttpStatus.SC_OK);
return resp.getTextFromJsonBody("/message").split(":")[1].trim();
}
}

public String createRawResourceAs(CertificateData adminCert) {
try (TestRestClient client = cluster.getRestClient(adminCert)) {
String sample = "{\"name\":\"sample\"}";
Expand All @@ -353,6 +383,12 @@ public TestRestClient.HttpResponse getResource(String resourceId, TestSecurityCo
}
}

public TestRestClient.HttpResponse getResourceGroup(String resourceGroupId, TestSecurityConfig.User user) {
try (TestRestClient client = cluster.getRestClient(user)) {
return client.get(SAMPLE_RESOURCE_GROUP_GET_ENDPOINT + "/" + resourceGroupId);
}
}

private void assertGet(String endpoint, TestSecurityConfig.User user, int status, String expectedString) {
try (TestRestClient client = cluster.getRestClient(user)) {
TestRestClient.HttpResponse response = client.get(endpoint);
Expand Down Expand Up @@ -484,6 +520,13 @@ public TestRestClient.HttpResponse updateResource(String resourceId, TestSecurit
}
}

public TestRestClient.HttpResponse updateResourceGroup(String resourceGroupId, TestSecurityConfig.User user, String newName) {
try (TestRestClient client = cluster.getRestClient(user)) {
String updatePayload = "{" + "\"name\": \"" + newName + "\"}";
return client.postJson(SAMPLE_RESOURCE_GROUP_UPDATE_ENDPOINT + "/" + resourceGroupId, updatePayload);
}
}

public void assertDirectUpdate(String resourceId, TestSecurityConfig.User user, String newName, int status) {
assertUpdate(RESOURCE_INDEX_NAME + "/_doc/" + resourceId + "?refresh=true", newName, user, status);
}
Expand Down Expand Up @@ -526,6 +569,20 @@ public TestRestClient.HttpResponse shareResource(
}
}

public TestRestClient.HttpResponse shareResourceGroup(
String resourceId,
TestSecurityConfig.User user,
TestSecurityConfig.User target,
String accessLevel
) {
try (TestRestClient client = cluster.getRestClient(user)) {
return client.putJson(
SECURITY_SHARE_ENDPOINT,
putSharingInfoPayload(resourceId, RESOURCE_GROUP_TYPE, accessLevel, Recipient.USERS, target.getName())
);
}
}

public TestRestClient.HttpResponse shareResourceByRole(
String resourceId,
TestSecurityConfig.User user,
Expand All @@ -540,6 +597,20 @@ public TestRestClient.HttpResponse shareResourceByRole(
}
}

public TestRestClient.HttpResponse shareResourceGroupByRole(
String resourceId,
TestSecurityConfig.User user,
String targetRole,
String accessLevel
) {
try (TestRestClient client = cluster.getRestClient(user)) {
return client.putJson(
SECURITY_SHARE_ENDPOINT,
putSharingInfoPayload(resourceId, RESOURCE_GROUP_TYPE, accessLevel, Recipient.ROLES, targetRole)
);
}
}

public TestRestClient.HttpResponse revokeResource(
String resourceId,
TestSecurityConfig.User user,
Expand All @@ -555,6 +626,21 @@ public TestRestClient.HttpResponse revokeResource(
}
}

public TestRestClient.HttpResponse revokeResourceGroup(
String resourceId,
TestSecurityConfig.User user,
TestSecurityConfig.User target,
String accessLevel
) {
PatchSharingInfoPayloadBuilder patchBuilder = new PatchSharingInfoPayloadBuilder();
patchBuilder.resourceType(RESOURCE_GROUP_TYPE);
patchBuilder.resourceId(resourceId);
patchBuilder.revoke(new Recipients(Map.of(Recipient.USERS, Set.of(target.getName()))), accessLevel);
try (TestRestClient client = cluster.getRestClient(user)) {
return client.patch(TestUtils.SECURITY_SHARE_ENDPOINT, patchBuilder.build());
}
}

public void assertDirectDelete(String resourceId, TestSecurityConfig.User user, int status) {
assertDelete(RESOURCE_INDEX_NAME + "/_doc/" + resourceId, user, status);
}
Expand All @@ -569,6 +655,12 @@ public TestRestClient.HttpResponse deleteResource(String resourceId, TestSecurit
}
}

public TestRestClient.HttpResponse deleteResourceGroup(String resourceGroupId, TestSecurityConfig.User user) {
try (TestRestClient client = cluster.getRestClient(user)) {
return client.delete(SAMPLE_RESOURCE_GROUP_DELETE_ENDPOINT + "/" + resourceGroupId);
}
}

private void assertDelete(String endpoint, TestSecurityConfig.User user, int status) {
try (TestRestClient client = cluster.getRestClient(user)) {
TestRestClient.HttpResponse response = client.delete(endpoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.opensearch.test.framework.cluster.TestRestClient;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.opensearch.sample.resource.TestUtils.NO_ACCESS_USER;
import static org.opensearch.sample.resource.TestUtils.RESOURCE_SHARING_INDEX;
Expand Down Expand Up @@ -54,10 +55,19 @@ public void testTypesApi_mustListSampleResourceAsAType() {
TestRestClient.HttpResponse response = client.get(SECURITY_TYPES_ENDPOINT);
response.assertStatusCode(HttpStatus.SC_OK);
List<Object> types = (List<Object>) response.bodyAsMap().get("types");
assertThat(types.size(), equalTo(1));
Map<String, Object> responseBody = (Map<String, Object>) types.getFirst();
assertThat(responseBody.get("type"), equalTo("sample-resource"));
assertThat(responseBody.get("action_groups"), equalTo(List.of("sample_read_only", "sample_read_write", "sample_full_access")));
assertThat(types.size(), equalTo(2));
Map<String, Object> firstType = (Map<String, Object>) types.get(0);
assertThat(firstType.get("type"), equalTo("sample-resource"));
assertThat(
(List<String>) firstType.get("action_groups"),
containsInAnyOrder("sample_read_only", "sample_read_write", "sample_full_access")
);
Map<String, Object> secondType = (Map<String, Object>) types.get(1);
assertThat(secondType.get("type"), equalTo("sample-resource-group"));
assertThat(
(List<String>) secondType.get("action_groups"),
containsInAnyOrder("sample_group_read_only", "sample_group_read_write", "sample_group_full_access")
);
}

}
Expand Down
Loading
Loading