Multi tenancy: fix for users without matching roles seeing all tenants data #950
Open
janblok wants to merge 5 commits into
Open
Multi tenancy: fix for users without matching roles seeing all tenants data #950janblok wants to merge 5 commits into
janblok wants to merge 5 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses a multi-tenancy data-leak in column-based multi-tenancy: when a user has no matching roleDataFilters for a table, the request should no longer return all tenants’ rows, and should instead restrict results to “shared/unscoped” rows (intended as tenant-column IS NULL). The implementation refactors how role data filters are passed from the auth layer into the API layer by attaching both the full filter list and the user’s roles, then deriving per-table filters inside MultiTenancy.
Changes:
- Refactor role-data-filter propagation to pass
(allRoleDataFilters, userRoles)via a request attribute and compute effective filters inMultiTenancy. - Add fallback behavior intended to apply an
IS NULLtenant filter when a table has a tenant column but the user has no matching role filter. - Update REST controllers/DTOs to accept the new
Pair<List<RoleDataFilter>, String[]>shape and add an integration test covering the “no matching role filter” scenario.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pom.xml | Adds compiler plugin configuration for Lombok annotation processing. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/provider/AbstractAuthProvider.java | Simplifies role data filter retrieval to return all filters from the lookup. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/datalookup/AuthDataLookup.java | Changes API to return all role data filters (no longer per-role). |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/datalookup/NoAuthdataLookup.java | Updates lookup implementation to new no-arg role filter method. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/datalookup/FileAuthDataLookup.java | Updates file-based lookup to return all role data filters. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/datalookup/ApiAuthDataLookup.java | Updates API-based lookup to return all role data filters. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/data/AuthData.java | Ensures roleDataFilters() accessor never returns null. |
| db2rest-auth/src/main/java/com/homihq/db2rest/auth/AuthFilter.java | Stores (roleDataFilters, userRoles) pair as a request attribute after authz. |
| db2rest-api/rest-common/src/main/java/com/homihq/db2rest/dtos/BulkContext.java | Updates bulk context to carry the new Pair-based role filter payload. |
| db2rest-api/rest-common/src/main/java/com/homihq/db2rest/config/MultiTenancy.java | Implements per-table role filter derivation and fallback IS NULL filtering. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/read/ReadController.java | Updates request attribute typing and applies multi-tenancy filtering consistently. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/read/FindOneController.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/read/ExistsQueryController.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/read/CountQueryController.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/update/UpdateController.java | Updates request attribute typing and continues applying tenant column enforcement and filtering. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/delete/DeleteRestApi.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/delete/DeleteController.java | Updates method signature to accept the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/create/CreateRestApi.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/create/CreateController.java | Updates method signature to accept the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/create/BulkCreateRestApi.java | Updates request attribute typing to the new Pair payload. |
| db2rest-api/api-rest/src/main/java/com/homihq/db2rest/rest/create/BulkCreateController.java | Updates bulk context creation to pass the new Pair payload. |
| db2rest-api/api-rest/src/test/java/com/homihq/db2rest/rest/pg/PgMultiTenancyTest.java | Adds coverage for querying as a user without matching role data filters. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
24
to
35
| if (dbId.equalsIgnoreCase(roleDataFilter.dbId()) && table.equalsIgnoreCase(roleDataFilter.table())) { | ||
| if (!filterBuilder.isEmpty()) filterBuilder.append(";"); | ||
| if (!filterBuilder.isEmpty()) | ||
| filterBuilder.append(";"); | ||
| filterBuilder.append(roleDataFilter.column()); | ||
| filterBuilder.append("=="); | ||
| if (roleDataFilter.value() == null) { | ||
| filterBuilder.append("=isnull="); | ||
| } | ||
| else { | ||
| filterBuilder.append("=="); | ||
| } | ||
| filterBuilder.append(roleDataFilter.value()); | ||
| } |
Comment on lines
+69
to
75
| public static void addTenantColumns(Map<String, Object> data, String dbId, String table, | ||
| Pair<List<RoleDataFilter>, String[]> roleBasedDataFilters) { | ||
| if (roleBasedDataFilters != null) { | ||
| for (RoleDataFilter roleDataFilter : roleBasedDataFilters) { | ||
| if (dbId.equalsIgnoreCase(roleDataFilter.dbId()) && table.equalsIgnoreCase(roleDataFilter.table())) { | ||
| data.put(roleDataFilter.column(), roleDataFilter.value()); | ||
| } | ||
| for (RoleDataFilter roleDataFilter : getUserRoleBasedDataFilters(dbId, table, | ||
| roleBasedDataFilters.getLeft(), roleBasedDataFilters.getRight())) { | ||
| data.put(roleDataFilter.column(), roleDataFilter.value()); | ||
| } |
Comment on lines
+173
to
+179
| <annotationProcessorPaths> | ||
| <path> | ||
| <groupId>org.projectlombok</groupId> | ||
| <artifactId>lombok</artifactId> | ||
| <version>${lombok.version}</version> | ||
| </path> | ||
| </annotationProcessorPaths> |
Comment on lines
+51
to
+59
| List<RoleDataFilter> retval = new ArrayList<>(); | ||
| for (String role : userRoles) { | ||
| retval.addAll(roleDataFilters.stream().filter(rd -> role.equalsIgnoreCase(rd.role())).toList()); | ||
| } | ||
| if (retval.isEmpty() && filterTenantColumn != null) { | ||
| // when a tenant column is defined for the table, but the user has no roles, we add a filter to only see null value rows | ||
| retval.add(new RoleDataFilter("see_null_values_only", dbId, table, filterTenantColumn, null)); | ||
| } | ||
| return retval; |
thadguidry
requested changes
Jun 27, 2026
| @RequestParam(name = "sort", required = false, defaultValue = "") List<String> sorts, | ||
| @RequestParam(required = false, defaultValue = "-1") int limit, | ||
| @RequestParam(required = false, defaultValue = "-1") long offset, | ||
| private final ReadService readService; |
Collaborator
…PgMultiTenancyTest.java Co-authored-by: Thad Guidry <thadmguidry@outlook.com>
…houtRole' into feature/roleFilterFixForUsersWithoutRole
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When using column based multi tenancy and a user does not have a role matching the roleDataFilters they can see all tenants data in a table having column based multi tenancy. This PR fixes this scenario by allowing them to see only the rows where the tenant id column value is not defined (is null) in case these are present.