-
-
Notifications
You must be signed in to change notification settings - Fork 145
feat: Introduce User Groups to Parseable #1366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis update introduces user group support to the RBAC system, enabling users to inherit roles through group memberships. Role assignment endpoints are refactored to separate add/remove operations, and metadata migration is extended to support user groups. Permission checks, API responses, and storage structures are updated to reflect group-based role inheritance and improved validation. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant RBAC
participant Metadata
participant UserGroups
Client->>API: PATCH /user/{username}/role/add (roles)
API->>RBAC: add_roles_to_user(username, roles)
RBAC->>Metadata: Update user roles in metadata
RBAC->>UserGroups: Update user group memberships if needed
RBAC-->>API: Success/Error
API-->>Client: Response
Client->>API: PATCH /user/{username}/role/remove (roles)
API->>RBAC: remove_roles_from_user(username, roles)
RBAC->>Metadata: Update user roles in metadata
RBAC->>UserGroups: Update user group memberships if needed
RBAC-->>API: Success/Error
API-->>Client: Response
Client->>API: GET /user/{username}/role
API->>RBAC: get_role(username)
RBAC->>UserGroups: Fetch group roles for user
RBAC-->>API: RolesResponse (direct + group roles)
API-->>Client: RolesResponse
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Clippy (1.86.0)
error: failed to get Caused by: Caused by: Caused by: Caused by: 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (6)
🧰 Additional context used🧠 Learnings (1)src/rbac/map.rs (2)
⏰ Context from checks skipped due to timeout of 90000ms (10)
🔇 Additional comments (11)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🔭 Outside diff range comments (1)
src/handlers/http/modal/ingest/ingestor_rbac.rs (1)
109-137
: Add role existence validation for consistency.The
add_roles_to_user
handler is missing role existence validation that's present in the main RBAC handlers (src/handlers/http/rbac.rs
lines 321-328). This could allow non-existent roles to be added to users.Add role validation before processing:
pub async fn add_roles_to_user( username: web::Path<String>, roles_to_add: web::Json<HashSet<String>>, ) -> Result<String, RBACError> { let username = username.into_inner(); let roles_to_add = roles_to_add.into_inner(); if !Users.contains(&username) { return Err(RBACError::UserDoesNotExist); }; + + // check if all roles exist + let mut non_existent_roles = Vec::new(); + roles_to_add.iter().for_each(|r| { + if roles().get(r).is_none() { + non_existent_roles.push(r.clone()); + } + }); + + if !non_existent_roles.is_empty() { + return Err(RBACError::RolesDoNotExist(non_existent_roles)); + } // update parseable.json first
🧹 Nitpick comments (6)
src/migration/mod.rs (1)
107-107
: Fix comment capitalization for consistency.- //remove querier endpoint and token from storage metadata + // Remove querier endpoint and token from storage metadatasrc/rbac/mod.rs (1)
119-137
: Correctly implements group-based permission inheritance.The method properly aggregates permissions from both direct roles and roles inherited through user groups.
Consider extracting the group permission aggregation logic into a helper method for better readability:
fn get_group_permissions(&self, username: &str) -> Vec<Permission> { let user_groups = self.get_user_groups(username); let mut permissions = Vec::new(); for group in user_groups { if let Some(group) = read_user_groups().get(&group) { for role in &group.roles { if let Some(privilege_list) = roles().get(role) { for privilege in privilege_list { permissions.extend(RoleBuilder::from(privilege).build()); } } } } } permissions }src/handlers/http/modal/query/querier_rbac.rs (2)
113-120
: Simplify role validation logic.The
map().for_each(drop)
pattern is unnecessarily complex for this use case.- user_roles - .iter() - .map(|r| { - if !roles().contains_key(r) { - non_existant_roles.push(r.clone()); - } - }) - .for_each(drop); + for r in user_roles.iter() { + if !roles().contains_key(r) { + non_existant_roles.push(r.clone()); + } + }
181-192
: Simplify user group removal logic.The
map().for_each(drop)
pattern is unnecessarily complex and reduces readability.- [&username] - .iter() - .map(|user| { - if let Some(user) = mut_users().get_mut(*user) { - for group in groups_to_update.iter() { - user.user_groups.remove(&group.name); - } - - metadata.users.retain(|u| u.username() != user.username()); - metadata.users.push(user.clone()); - } - }) - .for_each(drop); + if let Some(user) = mut_users().get_mut(&username) { + for group in groups_to_update.iter() { + user.user_groups.remove(&group.name); + } + + metadata.users.retain(|u| u.username() != user.username()); + metadata.users.push(user.clone()); + }src/handlers/http/rbac.rs (2)
119-126
: Simplify role validation logic.Use a standard for loop instead of the complex
map().for_each(drop)
pattern for better readability.- user_roles - .iter() - .map(|r| { - if !roles().contains_key(r) { - non_existant_roles.push(r.clone()); - } - }) - .for_each(drop); + for r in user_roles.iter() { + if !roles().contains_key(r) { + non_existant_roles.push(r.clone()); + } + }
254-266
: Simplify user metadata update logic.The
map().for_each(drop)
pattern is unnecessarily complex for updating a single user.- [&username] - .iter() - .map(|user| { - if let Some(user) = mut_users().get_mut(*user) { - for group in groups_to_update.iter() { - user.user_groups.remove(&group.name); - } - - metadata.users.retain(|u| u.username() != user.username()); - metadata.users.push(user.clone()); - } - }) - .for_each(drop); + if let Some(user) = mut_users().get_mut(&username) { + for group in groups_to_update.iter() { + user.user_groups.remove(&group.name); + } + + metadata.users.retain(|u| u.username() != user.username()); + metadata.users.push(user.clone()); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/handlers/http/cluster/mod.rs
(1 hunks)src/handlers/http/modal/ingest/ingestor_rbac.rs
(3 hunks)src/handlers/http/modal/ingest_server.rs
(1 hunks)src/handlers/http/modal/query/querier_rbac.rs
(5 hunks)src/handlers/http/modal/query_server.rs
(2 hunks)src/handlers/http/modal/server.rs
(2 hunks)src/handlers/http/oidc.rs
(1 hunks)src/handlers/http/rbac.rs
(9 hunks)src/handlers/http/role.rs
(3 hunks)src/migration/metadata_migration.rs
(1 hunks)src/migration/mod.rs
(4 hunks)src/rbac/map.rs
(6 hunks)src/rbac/mod.rs
(6 hunks)src/rbac/role.rs
(16 hunks)src/rbac/user.rs
(6 hunks)src/rbac/utils.rs
(3 hunks)src/storage/store_metadata.rs
(5 hunks)src/utils/mod.rs
(1 hunks)
🧰 Additional context used
🧠 Learnings (7)
src/utils/mod.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1340
File: src/query/mod.rs:64-66
Timestamp: 2025-06-18T06:39:04.775Z
Learning: In src/query/mod.rs, QUERY_SESSION_STATE and QUERY_SESSION serve different architectural purposes: QUERY_SESSION_STATE is used for stats calculation and allows dynamic registration of individual parquet files from the staging path (files created every minute), while QUERY_SESSION is used for object store queries with the global schema provider. Session contexts with schema providers don't support registering individual tables/parquets, so both session objects are necessary for their respective use cases.
src/handlers/http/cluster/mod.rs (2)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1346
File: src/handlers/http/health_check.rs:81-90
Timestamp: 2025-06-16T02:04:58.990Z
Learning: In the shutdown function in src/handlers/http/health_check.rs, the design approach is to log errors from sync operations rather than propagate them. This is intentional because the shutdown function is called on SIGTERM/SIGINT signals, and the goal is to perform best-effort cleanup (syncing pending files to object storage) while allowing the shutdown to proceed regardless of sync failures. Logging provides debugging information without blocking the shutdown process.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
src/handlers/http/modal/query_server.rs (3)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
Learnt from: de-sh
PR: parseablehq/parseable#1185
File: src/handlers/http/logstream.rs:255-261
Timestamp: 2025-02-14T09:49:25.818Z
Learning: In Parseable's logstream handlers, stream existence checks must be performed for both query and standalone modes. The pattern `!PARSEABLE.streams.contains(&stream_name) && (PARSEABLE.options.mode != Mode::Query || !PARSEABLE.create_stream_and_schema_from_storage(&stream_name).await?)` ensures proper error handling in both modes.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1346
File: src/parseable/streams.rs:319-331
Timestamp: 2025-06-16T09:50:38.636Z
Learning: In Parseable's Ingest or Query mode, the node_id is always available because it's generated during server initialization itself, before the get_node_id_string() function in streams.rs would be called. This makes the .expect() calls on QUERIER_META.get() and INGESTOR_META.get() safe in this context.
src/handlers/http/modal/ingest/ingestor_rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
src/rbac/map.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1340
File: src/query/mod.rs:64-66
Timestamp: 2025-06-18T06:39:04.775Z
Learning: In src/query/mod.rs, QUERY_SESSION_STATE and QUERY_SESSION serve different architectural purposes: QUERY_SESSION_STATE is used for stats calculation and allows dynamic registration of individual parquet files from the staging path (files created every minute), while QUERY_SESSION is used for object store queries with the global schema provider. Session contexts with schema providers don't support registering individual tables/parquets, so both session objects are necessary for their respective use cases.
src/handlers/http/modal/query/querier_rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
src/handlers/http/rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
🧬 Code Graph Analysis (3)
src/utils/mod.rs (1)
src/handlers/http/query.rs (1)
query
(121-171)
src/handlers/http/modal/ingest/ingestor_rbac.rs (4)
src/handlers/http/rbac.rs (3)
Users
(72-72)add_roles_to_user
(314-356)remove_roles_from_user
(359-413)src/rbac/user.rs (1)
username
(80-88)src/storage/store_metadata.rs (1)
put_staging_metadata
(286-301)src/handlers/http/oidc.rs (1)
get_metadata
(368-376)
src/rbac/mod.rs (3)
src/rbac/map.rs (8)
mut_sessions
(97-103)mut_users
(65-71)read_user_groups
(41-47)roles
(73-79)sessions
(89-95)users
(57-63)from
(299-307)from
(322-330)src/rbac/user.rs (5)
roles
(94-96)username
(80-88)add_roles
(267-279)remove_roles
(300-310)from
(188-197)src/rbac/role.rs (1)
from
(220-243)
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: Build Default x86_64-pc-windows-msvc
- GitHub Check: Build Default x86_64-apple-darwin
- GitHub Check: Build Default aarch64-apple-darwin
- GitHub Check: Build Kafka aarch64-apple-darwin
- GitHub Check: Build Default x86_64-unknown-linux-gnu
- GitHub Check: Build Kafka x86_64-unknown-linux-gnu
- GitHub Check: Build Default aarch64-unknown-linux-gnu
- GitHub Check: coverage
- GitHub Check: Quest Smoke and Load Tests for Distributed deployments
- GitHub Check: Quest Smoke and Load Tests for Standalone deployments
🔇 Additional comments (27)
src/handlers/http/oidc.rs (1)
336-340
: LGTM! Correct pattern matching update for user groups.The destructuring pattern correctly includes the new
user_groups
field with an ignore pattern, ensuring the match remains exhaustive while not affecting the existing logic.src/utils/mod.rs (1)
96-96
: LGTM! Correct extraction of tables from visitor.The
.into_inner()
call correctly extracts the tables vector from theTableScanVisitor
, matching the expected type foruser_auth_for_datasets
.src/migration/metadata_migration.rs (1)
164-185
: LGTM! Well-structured migration for user groups.The migration correctly:
- Updates version from v5 to v6
- Adds top-level
user_groups
field if missing- Adds
user_groups
field to each user object if missing- Uses appropriate default values (empty arrays)
The defensive checks ensure idempotency and prevent errors.
src/storage/store_metadata.rs (5)
35-35
: LGTM! Correct import for user groups support.Adding
UserGroup
import is necessary for the newuser_groups
field in theStorageMetadata
struct.
45-45
: LGTM! Version bump aligns with migration.Updating to "v6" is consistent with the new
v5_v6
migration function that adds user groups support.
63-63
: LGTM! User groups field addition is correct.The new
user_groups
field follows the same pattern as the existingusers
field and supports the user groups feature.
82-82
: LGTM! Consistent default initialization.Initializing
user_groups
to an emptyVec
is consistent with how other collection fields are handled in theDefault
implementation.
94-94
: Nice typo fix!Correcting "gloabal" to "global" improves code quality.
src/handlers/http/modal/ingest_server.rs (1)
201-219
: LGTM! API endpoint refactoring aligns with PR objectives.The splitting of role management into separate add and remove endpoints is well-implemented:
- Appropriate use of PATCH method for partial updates
- Consistent authorization (
Action::PutUserRoles
) and middleware (DisAllowRootUser
)- Clear, descriptive handler function names
- Proper route structure following RESTful conventions
This provides more granular control over role operations compared to the previous monolithic PUT endpoint.
src/handlers/http/role.rs (1)
91-104
: Fix logic error in role usage validation.The current logic checks if any group contains the role being deleted and returns
RoleInUse
error, but then continues to collect group names regardless. This creates inconsistent behavior where the function should either fail early or continue processing.Apply this diff to fix the logic:
- // also delete from user groups - let groups = read_user_groups().keys().cloned().collect_vec(); - let mut group_names = Vec::new(); - - for user_group in groups { - if let Some(ug) = read_user_groups().get(&user_group) { - if ug.roles.contains(&name) { - return Err(RoleError::RoleInUse); - } - group_names.push(ug.name.clone()); - } else { - continue; - }; - } + // check if any user group is using this role + let groups = read_user_groups(); + for group in groups.values() { + if group.roles.contains(&name) { + return Err(RoleError::RoleInUse); + } + }Likely an incorrect or invalid review comment.
src/handlers/http/cluster/mod.rs (1)
163-186
: LGTM: Clean implementation of operation-specific role synchronization.The addition of the
operation
parameter with proper validation and URL path construction correctly supports the new split role synchronization endpoints. The error handling follows the existing pattern and the validation ensures only valid operations are processed.src/handlers/http/modal/query_server.rs (2)
97-98
: LGTM: Correct mutation for metadata migration.Making
parseable_json
mutable before passing to the migration function allows for in-place updates, which aligns with the updated migration logic that adds user group support.
211-236
: LGTM: Well-structured role endpoint refactoring.The refactoring from a single PUT endpoint to separate GET/PATCH add/remove endpoints follows REST principles and provides better granular control over role operations. The authorization configuration is appropriate for each endpoint type.
src/rbac/utils.rs (1)
41-78
: LGTM: Comprehensive user group role integration.The enhancement to include user group roles in the
UsersPrism
response is well-implemented. The code properly:
- Collects roles from user groups the user belongs to
- Handles missing group references gracefully
- Organizes data into logical structures (direct roles vs group roles)
- Maintains consistency with existing role collection patterns
src/migration/mod.rs (2)
46-49
: LGTM!The parameter change to mutable reference enables in-place metadata updates, allowing the migration results to be propagated back to the caller.
75-79
: Well-structured migration flow.The consistent addition of the v5_v6 migration step across all version paths and the pattern of serializing results back to the mutable reference ensures proper metadata updates.
Also applies to: 87-91, 98-102, 110-115, 118-122
src/handlers/http/modal/server.rs (2)
117-118
: LGTM!The change correctly propagates the mutable reference requirement from the migration module.
611-636
: Role endpoints updated; old PUT handler is commented out
Verified that the previousput_role
handler is no longer active and has been commented out in:
- src/handlers/http/rbac.rs
- src/handlers/http/modal/query/querier_rbac.rs
- src/handlers/http/modal/ingest/ingestor_rbac.rs
No further changes are required.
src/rbac/user.rs (3)
28-35
: LGTM!The User struct is properly extended with the
user_groups
field, and all constructors correctly initialize it as an empty set.Also applies to: 48-50, 63-64, 76-77, 160-161
200-262
: Well-designed UserGroup struct with comprehensive validation.The validation method properly checks for:
- Valid group name format using regex
- Existence of the group (preventing duplicates)
- Existence of all referenced roles and users
- Provides detailed error information
300-331
: LGTM!The remove methods correctly use set operations and optimize for no-op cases. The
update_in_metadata
method properly replaces the group entry.src/rbac/mod.rs (3)
32-32
: LGTM!The
get_user_groups
method correctly retrieves user group memberships with proper error handling for missing users.Also applies to: 57-63
100-113
: Clean separation of role operations.The split into
add_roles
andremove_roles
provides clearer semantics than a single update method. Session invalidation correctly ensures permission changes take immediate effect.
207-224
: LGTM!The
UsersPrism
struct comprehensively represents users with:
- Direct roles
- Group-inherited roles
- Group memberships
The camelCase serialization maintains API consistency.
src/rbac/map.rs (2)
39-55
: LGTM!The
USER_GROUPS
map follows the established pattern for global state management with proper initialization and thread-safe accessors.Also applies to: 111-111, 144-146
310-331
: LGTM!The
UserGroups
struct implementation is clean and follows the established pattern from theUsers
struct.src/rbac/role.rs (1)
23-26
: Well-structured RBAC model extensions.The additions properly extend the RBAC system to support:
- User group management actions
- Resource-scoped permissions with optional resource type and ID
- Consistent builder pattern implementation
The integration with LLM actions and the DefaultPrivilege enum is clean and follows existing patterns.
Also applies to: 85-85, 95-96, 111-115, 167-175, 213-216
let mut groups_to_update = Vec::new(); | ||
for user_group in group_names { | ||
if let Some(ug) = write_user_groups().get_mut(&user_group) { | ||
ug.remove_roles(HashSet::from_iter([name.clone()])) | ||
.map_err(|e| RoleError::Anyhow(anyhow::Error::msg(e.to_string())))?; | ||
groups_to_update.push(ug.clone()); | ||
// ug.update_in_metadata().await?; | ||
} else { | ||
continue; | ||
}; | ||
} | ||
|
||
// update in metadata | ||
metadata | ||
.user_groups | ||
.retain(|x| !groups_to_update.contains(x)); | ||
metadata.user_groups.extend(groups_to_update); | ||
put_metadata(&metadata).await?; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize user group role removal logic.
The current implementation has redundant operations and potential inconsistencies. The group collection and role removal can be simplified and made more robust.
Apply this diff to improve the logic:
- let mut groups_to_update = Vec::new();
- for user_group in group_names {
- if let Some(ug) = write_user_groups().get_mut(&user_group) {
- ug.remove_roles(HashSet::from_iter([name.clone()]))
- .map_err(|e| RoleError::Anyhow(anyhow::Error::msg(e.to_string())))?;
- groups_to_update.push(ug.clone());
- // ug.update_in_metadata().await?;
- } else {
- continue;
- };
- }
-
- // update in metadata
- metadata
- .user_groups
- .retain(|x| !groups_to_update.contains(x));
- metadata.user_groups.extend(groups_to_update);
+ // remove role from all user groups that have it
+ let mut groups_to_update = Vec::new();
+ for group in write_user_groups().values_mut() {
+ if group.roles.remove(&name) {
+ groups_to_update.push(group.clone());
+ }
+ }
+
+ // update metadata only if there are changes
+ if !groups_to_update.is_empty() {
+ metadata
+ .user_groups
+ .retain(|x| !groups_to_update.contains(x));
+ metadata.user_groups.extend(groups_to_update);
+ }
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/handlers/http/role.rs around lines 106 to 124, the current logic for
removing roles from user groups is redundant and can cause inconsistencies by
cloning and manually managing group updates. Refactor by iterating over
group_names, directly removing roles from the mutable user groups obtained from
write_user_groups(), and then updating metadata.user_groups in a single,
consistent step without cloning. Ensure the removal and metadata update are done
atomically to avoid stale data and simplify the flow.
pub async fn remove_roles_from_user( | ||
username: web::Path<String>, | ||
roles_to_remove: web::Json<HashSet<String>>, | ||
) -> Result<String, RBACError> { | ||
let username = username.into_inner(); | ||
let roles_to_remove = roles_to_remove.into_inner(); | ||
|
||
if !Users.contains(&username) { | ||
return Err(RBACError::UserDoesNotExist); | ||
}; | ||
// update parseable.json first | ||
let mut metadata = get_metadata().await?; | ||
if let Some(user) = metadata | ||
.users | ||
.iter_mut() | ||
.find(|user| user.username() == username) | ||
{ | ||
let diff: HashSet<String> = | ||
HashSet::from_iter(user.roles.difference(&roles_to_remove).cloned()); | ||
user.roles = diff; | ||
} else { | ||
// should be unreachable given state is always consistent | ||
return Err(RBACError::UserDoesNotExist); | ||
} | ||
|
||
let _ = storage::put_staging_metadata(&metadata); | ||
// update in mem table | ||
Users.remove_roles(&username.clone(), roles_to_remove.clone()); | ||
|
||
Ok(format!("Roles updated successfully for {username}")) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add comprehensive validation for role removal.
The remove_roles_from_user
handler is missing both role existence validation and checks to ensure the roles are actually assigned to the user before removal.
Add proper validation:
pub async fn remove_roles_from_user(
username: web::Path<String>,
roles_to_remove: web::Json<HashSet<String>>,
) -> Result<String, RBACError> {
let username = username.into_inner();
let roles_to_remove = roles_to_remove.into_inner();
if !Users.contains(&username) {
return Err(RBACError::UserDoesNotExist);
};
+
+ // check if all roles exist
+ let mut non_existent_roles = Vec::new();
+ roles_to_remove.iter().for_each(|r| {
+ if roles().get(r).is_none() {
+ non_existent_roles.push(r.clone());
+ }
+ });
+
+ if !non_existent_roles.is_empty() {
+ return Err(RBACError::RolesDoNotExist(non_existent_roles));
+ }
+
+ // check that user actually has these roles
+ let user_roles: HashSet<String> = HashSet::from_iter(Users.get_role(&username));
+ let roles_not_with_user: HashSet<String> =
+ HashSet::from_iter(roles_to_remove.difference(&user_roles).cloned());
+ if !roles_not_with_user.is_empty() {
+ return Err(RBACError::RolesNotAssigned(Vec::from_iter(roles_not_with_user)));
+ }
// update parseable.json first
🤖 Prompt for AI Agents
In src/handlers/http/modal/ingest/ingestor_rbac.rs between lines 140 and 170,
the remove_roles_from_user function lacks validation to check if the roles to be
removed actually exist and if they are currently assigned to the user. To fix
this, first verify that each role in roles_to_remove exists in the system's role
list, returning an error if any do not exist. Then check that all roles to
remove are currently assigned to the user before proceeding; if any are not
assigned, return an appropriate error. Implement these validations before
modifying the user's roles and updating metadata.
src/migration/mod.rs
Outdated
@@ -62,6 +62,7 @@ pub async fn run_metadata_migration( | |||
.and_then(|version| version.as_str()) | |||
} | |||
|
|||
warn!(verion=?get_version(storage_metadata.as_ref().unwrap())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo in log field name.
- warn!(verion=?get_version(storage_metadata.as_ref().unwrap()));
+ warn!(version=?get_version(storage_metadata.as_ref().unwrap()));
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
warn!(verion=?get_version(storage_metadata.as_ref().unwrap())); | |
warn!(version=?get_version(storage_metadata.as_ref().unwrap())); |
🤖 Prompt for AI Agents
In src/migration/mod.rs at line 65, there is a typo in the log field name
"verion". Correct the typo by changing "verion" to "version" in the warn! macro
call to ensure the log field name is accurate.
src/rbac/user.rs
Outdated
pub async fn add_users(&mut self, users: HashSet<String>) -> Result<(), RBACError> { | ||
// ensure that the users add the user group to their map | ||
let mut metadata = get_metadata().await?; | ||
|
||
users | ||
.iter() | ||
.map(|user| { | ||
if let Some(user) = mut_users().get_mut(user) { | ||
user.user_groups.insert(self.name.clone()); | ||
metadata.users.retain(|u| u.username() != user.username()); | ||
metadata.users.push(user.clone()); | ||
} | ||
}) | ||
.for_each(drop); | ||
put_metadata(&metadata).await?; | ||
self.users.extend(users); | ||
Ok(()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider improving error handling for consistency.
The add_users
method modifies the global user map before attempting to persist metadata. If put_metadata
fails, the in-memory state will be inconsistent with the persisted state.
Consider either:
- Clone the users before modification and only update the global map after successful metadata persistence
- Add a rollback mechanism if metadata update fails
- Document this behavior as a known limitation
Example approach:
pub async fn add_users(&mut self, users: HashSet<String>) -> Result<(), RBACError> {
let mut metadata = get_metadata().await?;
// Update metadata first
for user in &users {
if let Some(user_meta) = metadata.users.iter_mut().find(|u| u.username() == user) {
// Update in metadata
// Note: This assumes user_groups field exists in metadata user representation
}
}
// Persist metadata
put_metadata(&metadata).await?;
// Only then update in-memory state
for user in &users {
if let Some(user) = mut_users().get_mut(user) {
user.user_groups.insert(self.name.clone());
}
}
self.users.extend(users);
Ok(())
}
🤖 Prompt for AI Agents
In src/rbac/user.rs around lines 281 to 298, the add_users method updates the
global user map before persisting metadata, risking inconsistency if
put_metadata fails. To fix this, first update the metadata users collection with
the new user group information, then call put_metadata to persist changes. Only
after successful persistence, update the in-memory global user map and
self.users. This ensures in-memory state remains consistent with persisted
metadata or consider adding rollback if persistence fails.
src/rbac/map.rs
Outdated
// if user is a part of any user groups, then add permissions | ||
let perms: HashSet<Permission> = | ||
if !users().0.get(username).unwrap().user_groups.is_empty() { | ||
let groups = users().0.get(username).unwrap().user_groups.clone(); | ||
let all_groups_roles = groups | ||
.iter() | ||
.filter(|id| (read_user_groups().0.contains_key(*id))) | ||
.map(|id| read_user_groups().0.get(id).unwrap().roles.clone()) | ||
.reduce(|mut acc, e| { | ||
acc.extend(e); | ||
acc | ||
}) | ||
.unwrap_or_default(); | ||
let mut privilege_list = Vec::new(); | ||
all_groups_roles | ||
.iter() | ||
.filter_map(|role| roles().get(role).cloned()) | ||
.for_each(|privileges| privilege_list.extend(privileges)); | ||
|
||
let mut perms = HashSet::from_iter(perms.clone()); | ||
for privs in privilege_list { | ||
perms.extend(RoleBuilder::from(&privs).build()) | ||
} | ||
perms | ||
} else { | ||
HashSet::from_iter(perms.clone()) | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Address potential panic and improve readability.
The permission aggregation logic has issues that need attention:
- Potential panic: Line 230 uses
unwrap()
which could panic if the user is not found - Redundant lookups: The user is looked up multiple times
- Complex nesting: The logic is hard to follow
Suggested refactor:
// Extract group permission aggregation to a helper
fn aggregate_group_permissions(username: &str) -> HashSet<Permission> {
let mut group_perms = HashSet::new();
let Some(user) = users().get(username) else {
return group_perms;
};
if user.user_groups.is_empty() {
return group_perms;
}
for group_name in &user.user_groups {
let Some(group) = read_user_groups().get(group_name) else {
continue;
};
for role_name in &group.roles {
let Some(privileges) = roles().get(role_name) else {
continue;
};
for privilege in privileges {
group_perms.extend(RoleBuilder::from(privilege).build());
}
}
}
group_perms
}
// In check_auth:
let mut perms = HashSet::from_iter(perms.clone());
perms.extend(aggregate_group_permissions(username));
🤖 Prompt for AI Agents
In src/rbac/map.rs around lines 228 to 254, the current permission aggregation
code uses unwrap() on user lookups which can panic if the user is missing,
performs redundant user lookups, and has complex nested logic that reduces
readability. Refactor by extracting the group permission aggregation into a
helper function that safely checks for user existence using pattern matching
(e.g., let Some(user) = users().get(username) else { return empty set; }),
iterates over user groups and their roles with safe lookups using pattern
matching to avoid panics, and accumulates permissions into a HashSet. Then, in
the main function, initialize perms from the existing set and extend it with the
helper's result. This improves safety and clarity.
- Added support for user groups - Migrated `PUT /user/{username}/role` to `PATCH /user/{username}/role/add` and `PATCH /user/{username}/role/remove`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/rbac/map.rs (1)
228-254
: Address potential panic and improve readability.The permission aggregation logic has issues that need attention:
- Potential panic: Line 234 uses
unwrap()
which could panic if the user group is not found- Redundant lookups: The user group is looked up multiple times
- Complex nesting: The logic is hard to follow
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/handlers/http/modal/query/querier_rbac.rs
(5 hunks)src/handlers/http/rbac.rs
(9 hunks)src/rbac/map.rs
(6 hunks)
🧰 Additional context used
🧠 Learnings (3)
src/handlers/http/modal/query/querier_rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
src/rbac/map.rs (2)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1340
File: src/query/mod.rs:64-66
Timestamp: 2025-06-18T06:39:04.775Z
Learning: In src/query/mod.rs, QUERY_SESSION_STATE and QUERY_SESSION serve different architectural purposes: QUERY_SESSION_STATE is used for stats calculation and allows dynamic registration of individual parquet files from the staging path (files created every minute), while QUERY_SESSION is used for object store queries with the global schema provider. Session contexts with schema providers don't support registering individual tables/parquets, so both session objects are necessary for their respective use cases.
src/handlers/http/rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
⏰ Context from checks skipped due to timeout of 90000ms (10)
- GitHub Check: coverage
- GitHub Check: Build Default x86_64-pc-windows-msvc
- GitHub Check: Build Default x86_64-apple-darwin
- GitHub Check: Build Default x86_64-unknown-linux-gnu
- GitHub Check: Build Default aarch64-apple-darwin
- GitHub Check: Build Kafka aarch64-apple-darwin
- GitHub Check: Build Default aarch64-unknown-linux-gnu
- GitHub Check: Build Kafka x86_64-unknown-linux-gnu
- GitHub Check: Quest Smoke and Load Tests for Standalone deployments
- GitHub Check: Quest Smoke and Load Tests for Distributed deployments
🔇 Additional comments (11)
src/rbac/map.rs (3)
39-55
: LGTM: User groups integration follows established patterns.The new
USER_GROUPS
static and accessor functions follow the same safe pattern used for other RBAC maps in this module. The initialization and read/write guards are consistent with existing code.
111-111
: LGTM: Proper initialization of user groups from metadata.The integration of user groups initialization in the
init
function is well-placed and follows the same pattern as other RBAC components.Also applies to: 144-146
310-331
: LGTM: Clean UserGroups implementation.The
UserGroups
struct implementation follows the same pattern as the existingUsers
struct, providing consistent API design across the RBAC module.src/handlers/http/modal/query/querier_rbac.rs (3)
62-75
: LGTM: Improved role validation prevents invalid user creation.The addition of role existence validation before user creation is a valuable improvement that prevents creating users with non-existent roles. The error handling appropriately returns a list of invalid roles.
96-100
: LGTM: Consistent with new role management API.The switch from
put_role
toadd_roles_to_user
aligns with the new granular role management approach and maintains consistency across the codebase.
160-205
: LGTM: Well-implemented granular role management.Both
add_roles_to_user
andremove_roles_from_user
handlers include comprehensive validation:
- Role existence validation
- User existence validation
- Role assignment validation (for removal)
- Proper metadata synchronization with ingestors
The separation of add/remove operations provides better granular control and clearer API semantics.
Also applies to: 207-264
src/handlers/http/rbac.rs (5)
117-130
: LGTM: Consistent role validation across handlers.The role existence validation in
post_user
matches the implementation in the querier module, ensuring consistent behavior across the RBAC API.
195-226
: Excellent enhancement: Comprehensive role information.The enhanced
get_role
handler now provides both direct user roles and inherited group roles in a well-structuredRolesResponse
. This gives clients complete visibility into a user's effective permissions, which is crucial for RBAC systems.
383-444
: LGTM: Comprehensive error handling for user groups.The new error variants and
InvalidUserGroupError
struct provide detailed error information that will help clients understand and correct validation issues. The structured approach to error reporting is well-designed.
467-489
: LGTM: Enhanced error responses with structured JSON.The improved error response handling returns structured JSON for role-related errors, making the API more client-friendly and easier to parse programmatically.
491-497
: LGTM: Well-designed response structure.The
RolesResponse
struct clearly separates direct roles from group roles while maintaining the role-to-privileges mapping, providing comprehensive role information in a logical structure.
// also delete from user groups | ||
let user_groups = Users.get_user_groups(&username); | ||
let mut groups_to_update = Vec::new(); | ||
for user_group in user_groups { | ||
if let Some(ug) = write_user_groups().get_mut(&user_group) { | ||
ug.remove_users(HashSet::from_iter([username.clone()]))?; | ||
groups_to_update.push(ug.clone()); | ||
// ug.update_in_metadata().await?; | ||
} else { | ||
continue; | ||
}; | ||
} | ||
|
||
// ensure that the users remove the user group from their map | ||
[&username] | ||
.iter() | ||
.map(|user| { | ||
if let Some(user) = mut_users().get_mut(*user) { | ||
for group in groups_to_update.iter() { | ||
user.user_groups.remove(&group.name); | ||
} | ||
|
||
metadata.users.retain(|u| u.username() != user.username()); | ||
metadata.users.push(user.clone()); | ||
} | ||
}) | ||
.for_each(drop); | ||
put_metadata(&metadata).await?; | ||
|
||
// update in metadata user group | ||
metadata | ||
.user_groups | ||
.retain(|x| !groups_to_update.contains(x)); | ||
metadata.user_groups.extend(groups_to_update); | ||
put_metadata(&metadata).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify user group cleanup logic in delete_user.
The user group cleanup logic is overly complex and could be simplified. The current implementation performs multiple metadata updates and has nested logic that's hard to follow.
Consider this simplified approach:
// also delete from user groups
let user_groups = Users.get_user_groups(&username);
-let mut groups_to_update = Vec::new();
-for user_group in user_groups {
- if let Some(ug) = write_user_groups().get_mut(&user_group) {
- ug.remove_users(HashSet::from_iter([username.clone()]))?;
- groups_to_update.push(ug.clone());
- // ug.update_in_metadata().await?;
- } else {
- continue;
- };
-}
-
-// ensure that the users remove the user group from their map
-[&username]
- .iter()
- .map(|user| {
- if let Some(user) = mut_users().get_mut(*user) {
- for group in groups_to_update.iter() {
- user.user_groups.remove(&group.name);
- }
-
- metadata.users.retain(|u| u.username() != user.username());
- metadata.users.push(user.clone());
- }
- })
- .for_each(drop);
-put_metadata(&metadata).await?;
-
-// update in metadata user group
-metadata
- .user_groups
- .retain(|x| !groups_to_update.contains(x));
-metadata.user_groups.extend(groups_to_update);
+for user_group in user_groups {
+ if let Some(ug) = write_user_groups().get_mut(&user_group) {
+ ug.remove_users(HashSet::from_iter([username.clone()]))?;
+ }
+}
+
+// Update user groups in metadata
+for group in write_user_groups().values() {
+ metadata.user_groups.retain(|ug| ug.name != group.name);
+ metadata.user_groups.push(group.clone());
+}
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/handlers/http/modal/query/querier_rbac.rs lines 117 to 151, the user
group cleanup logic in delete_user is overly complex with multiple metadata
updates and nested loops. Simplify by consolidating the removal of the user from
user groups and updating metadata in a single pass. Remove redundant cloning and
repeated calls to put_metadata by batching changes and updating metadata once
after all modifications are done. Refactor the iteration to directly update user
groups and users without unnecessary intermediate collections or nested mapping.
src/handlers/http/rbac.rs
Outdated
// also delete from user groups | ||
let user_groups = Users.get_user_groups(&username); | ||
let mut groups_to_update = Vec::new(); | ||
for user_group in user_groups { | ||
if let Some(ug) = write_user_groups().get_mut(&user_group) { | ||
ug.remove_users(HashSet::from_iter([username.clone()]))?; | ||
groups_to_update.push(ug.clone()); | ||
// ug.update_in_metadata().await?; | ||
} else { | ||
continue; | ||
}; | ||
} | ||
|
||
// ensure that the users remove the user group from their map | ||
[&username] | ||
.iter() | ||
.map(|user| { | ||
if let Some(user) = mut_users().get_mut(*user) { | ||
for group in groups_to_update.iter() { | ||
user.user_groups.remove(&group.name); | ||
} | ||
|
||
metadata.users.retain(|u| u.username() != user.username()); | ||
metadata.users.push(user.clone()); | ||
} | ||
}) | ||
.for_each(drop); | ||
put_metadata(&metadata).await?; | ||
|
||
// update in metadata user groups | ||
metadata | ||
.user_groups | ||
.retain(|x| !groups_to_update.contains(x)); | ||
metadata.user_groups.extend(groups_to_update); | ||
put_metadata(&metadata).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Similar complexity issue as in querier module.
The user group cleanup logic here has the same complexity issues as identified in the querier module. Consider applying a similar simplification approach.
Refer to the refactoring suggestion provided for the querier module's delete_user
handler.
🤖 Prompt for AI Agents
In src/handlers/http/rbac.rs between lines 240 and 274, the user group cleanup
logic has high complexity due to nested loops and repeated metadata updates.
Simplify by collecting all necessary changes first, then perform a single update
to metadata. Avoid cloning and multiple retains by using more direct data
structure operations, similar to the approach used in the querier module's
delete_user handler refactoring. This will reduce complexity and improve
readability.
- Introduce resource types (stream, llm, all) for privileges
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/rbac/map.rs (1)
228-255
: Address potential panic and improve readability.The permission aggregation logic has issues that need attention as previously identified.
src/handlers/http/rbac.rs (1)
240-257
: Similar complexity issue as in querier module.The user group cleanup logic here has the same complexity issues as identified in the querier module.
🧹 Nitpick comments (4)
src/rbac/role.rs (2)
93-94
: Remove commented-out code.These commented lines represent the old implementation and should be removed to maintain code clarity:
- Lines 93-94: Old Permission variants
- Lines 103-105: Old RoleBuilder fields
- Lines 121-135: Old RoleBuilder methods
pub enum Permission { Unit(Action), - // Stream(Action, String), - // StreamWithTag(Action, String, Option<String>), Resource(Action, ParseableResourceType), SelfUser, } pub struct RoleBuilder { actions: Vec<Action>, - // stream: Option<String>, - // tag: Option<String>, - // resource_id: Option<String>, resource_type: Option<ParseableResourceType>, } - // pub fn with_stream(mut self, stream: String) -> Self { - // self.stream = Some(stream); - // self - // } - - // pub fn with_tag(mut self, tag: String) -> Self { - // self.tag = Some(tag); - // self - // } - - // pub fn with_resource(mut self, resource_id: String, resource_type: ParseableResourceType) -> Self { - // self.resource_id = Some(resource_id); - // self.resource_type = Some(resource_type); - // self - // }Also applies to: 103-105, 121-135
258-261
: Remove more commented-out code.Additional commented lines from the old implementation should be removed:
RoleBuilder { actions: vec![Action::All], - // stream: Some("*".to_string()), - // tag: None, resource_type: Some(ParseableResourceType::All), - // resource_id: Some("*".to_string()), } ], - // stream: Some("*".to_string()), - // tag: None, - // resource_id: Some("*".to_string()), resource_type: Some(ParseableResourceType::All), ], - // stream: None, - // tag: None, - // resource_id: None, resource_type: None, ], - // stream: None, - // tag: None, - // resource_id: None, resource_type: None, actions: vec![Action::Ingest], - // stream: None, - // tag: None, - // resource_id: None, resource_type: None,Also applies to: 307-310, 350-353, 386-389, 396-399
src/handlers/http/rbac.rs (2)
119-127
: Use more idiomatic code for role validation.The current implementation uses
.map()
with.for_each(drop)
which is unconventional. Consider a more idiomatic approach:- let mut non_existant_roles = Vec::new(); - user_roles - .iter() - .map(|r| { - if !roles().contains_key(r) { - non_existant_roles.push(r.clone()); - } - }) - .for_each(drop); + let non_existant_roles: Vec<String> = user_roles + .iter() + .filter(|r| !roles().contains_key(*r)) + .cloned() + .collect();
277-288
: Extract role validation to reduce duplication.The role existence validation is duplicated between
add_roles_to_user
andremove_roles_from_user
. Consider extracting to a helper function:fn validate_roles_exist(roles: &HashSet<String>) -> Result<(), RBACError> { let non_existant_roles: Vec<String> = roles .iter() .filter(|r| roles().get(*r).is_none()) .cloned() .collect(); if !non_existant_roles.is_empty() { return Err(RBACError::RolesDoNotExist(non_existant_roles)); } Ok(()) }Then use it in both handlers:
validate_roles_exist(&roles_to_add)?;Also applies to: 322-333
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/handlers/http/modal/query/querier_rbac.rs
(5 hunks)src/handlers/http/rbac.rs
(9 hunks)src/migration/mod.rs
(2 hunks)src/rbac/map.rs
(6 hunks)src/rbac/role.rs
(10 hunks)src/utils/mod.rs
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/utils/mod.rs
- src/migration/mod.rs
- src/handlers/http/modal/query/querier_rbac.rs
🧰 Additional context used
🧠 Learnings (2)
src/rbac/map.rs (2)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1340
File: src/query/mod.rs:64-66
Timestamp: 2025-06-18T06:39:04.775Z
Learning: In src/query/mod.rs, QUERY_SESSION_STATE and QUERY_SESSION serve different architectural purposes: QUERY_SESSION_STATE is used for stats calculation and allows dynamic registration of individual parquet files from the staging path (files created every minute), while QUERY_SESSION is used for object store queries with the global schema provider. Session contexts with schema providers don't support registering individual tables/parquets, so both session objects are necessary for their respective use cases.
src/handlers/http/rbac.rs (1)
Learnt from: nikhilsinhaparseable
PR: parseablehq/parseable#1305
File: src/handlers/http/users/dashboards.rs:0-0
Timestamp: 2025-05-01T10:27:56.858Z
Learning: The `add_tile()` function in `src/handlers/http/users/dashboards.rs` should use `get_dashboard_by_user(dashboard_id, &user_id)` instead of `get_dashboard(dashboard_id)` to ensure proper authorization checks when modifying a dashboard.
⏰ Context from checks skipped due to timeout of 90000ms (9)
- GitHub Check: Quest Smoke and Load Tests for Standalone deployments
- GitHub Check: Quest Smoke and Load Tests for Distributed deployments
- GitHub Check: Build Default x86_64-pc-windows-msvc
- GitHub Check: Build Default x86_64-unknown-linux-gnu
- GitHub Check: Build Default aarch64-apple-darwin
- GitHub Check: Build Default aarch64-unknown-linux-gnu
- GitHub Check: Build Default x86_64-apple-darwin
- GitHub Check: Build Kafka x86_64-unknown-linux-gnu
- GitHub Check: Build Kafka aarch64-apple-darwin
🔇 Additional comments (5)
src/rbac/map.rs (3)
40-56
: LGTM!The USER_GROUPS static and accessor functions follow the established patterns in the codebase.
268-269
: Clarify the permission logic when context_resource is None.The comment "WHEN IS THIS VALID??" indicates uncertainty about this code path. When
context_resource
is None, the code grants permission regardless of the resource_id. This could be a security concern if not intentional.Please verify:
- Under what conditions is
context_resource
expected to be None?- Is it correct to grant permission when no resource context is provided?
- Should this case log a warning or require explicit handling?
319-340
: LGTM!The UserGroups struct implementation follows the established pattern used for Users.
src/handlers/http/rbac.rs (2)
195-225
: LGTM!The implementation correctly returns both direct roles and roles inherited from user groups, providing a comprehensive view of user permissions.
452-471
: LGTM!The structured JSON error responses provide clear feedback to API consumers, improving the developer experience.
| Action::All => Permission::Resource( | ||
action, | ||
self.resource_type.clone().unwrap(), | ||
// self.resource_id.clone().unwrap(), | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential panic from unwrap().
The unwrap()
on line 197 will panic if resource_type
is None. This is unsafe since not all code paths ensure resource_type
is set before calling build()
.
| Action::All => Permission::Resource(
action,
- self.resource_type.clone().unwrap(),
+ self.resource_type.clone().unwrap_or(ParseableResourceType::All),
),
Alternatively, consider making resource_type
a required parameter for actions that need it, or return a Result
from build()
to handle the error case properly.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Action::All => Permission::Resource( | |
action, | |
self.resource_type.clone().unwrap(), | |
// self.resource_id.clone().unwrap(), | |
), | |
| Action::All => Permission::Resource( | |
action, | |
self.resource_type.clone().unwrap_or(ParseableResourceType::All), | |
// self.resource_id.clone().unwrap(), | |
), |
🤖 Prompt for AI Agents
In src/rbac/role.rs around lines 195 to 199, the use of unwrap() on
resource_type can cause a panic if resource_type is None. To fix this, avoid
using unwrap() directly; instead, handle the None case safely by either
returning a Result from the build() function to propagate the error or by making
resource_type a required parameter for actions that need it, ensuring it is
always set before calling build().
- roles don't need any migration - auth flow modified to account for resource type
PUT /user/{username}/role
toPATCH /user/{username}/role/add
andPATCH /user/{username}/role/remove
Fixes #XXXX.
Description
This PR has:
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores