Skip to content

Conversation

nikhilsinhaparseable
Copy link
Contributor

@nikhilsinhaparseable nikhilsinhaparseable commented Sep 10, 2025

this is helpful when an admin wants to create a user and assign group to it

Summary by CodeRabbit

  • New Features

    • You can now create a user without assigning any roles (empty request accepted).
  • Improvements

    • Duplicate-username errors now include the conflicting username for easier troubleshooting.
    • Skips role assignment when no roles are provided, reducing unnecessary processing.
  • Documentation

    • Corrected endpoint method references in comments from PUT to POST to match the actual route.

this is helpful when an admin wants to create a user
and assign group to it
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Allows creating users without roles by treating missing bodies as empty sets, updates role validation to collect non-existent roles, conditionally assigns roles post-creation, and changes RBACError::UserExists to carry the username. Updates comments to reflect POST semantics in modal server and query server.

Changes

Cohort / File(s) Summary of Changes
RBAC user creation logic
src/handlers/http/rbac.rs, src/handlers/http/modal/query/querier_rbac.rs
post_user now accepts empty/missing role sets; collects non-existent roles during validation; returns RBACError::UserExists(username); conditionally calls add_roles_to_user only when roles exist; renames variable to non_existent_roles; updates error formatting and matching for payload variant.
Route comment corrections (POST vs PUT)
src/handlers/http/modal/query_server.rs, src/handlers/http/modal/server.rs
Corrected inline/docs comments to state POST for /user/{username}; no functional/routing changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant HTTP Server as HTTP Server (/user/{username})
  participant RBAC as RBAC Handler
  participant Store as Querier/Storage

  Note over Client,HTTP Server: POST /user/{username} (body may be absent)

  Client->>HTTP Server: POST username [+ optional roles]
  HTTP Server->>RBAC: post_user(username, body)

  alt Body missing
    RBAC->>RBAC: Treat roles = {}
  end

  RBAC->>Store: Check user exists(username)
  alt User exists
    Store-->>RBAC: Exists
    RBAC-->>HTTP Server: Err UserExists(username)
    HTTP Server-->>Client: 4xx RBACError::UserExists(username)
  else Not exists
    Store-->>RBAC: Not found
    RBAC->>Store: Validate roles (lookup each)
    alt Any role missing
      Store-->>RBAC: Missing role(s)
      RBAC-->>HTTP Server: Err RolesDoNotExist([...])
      HTTP Server-->>Client: 4xx error
    else All roles valid (or none provided)
      RBAC->>Store: Create user(username)
      alt roles not empty
        RBAC->>Store: add_roles_to_user(username, roles)
      else roles empty
        Note right of RBAC: Skip role assignment
      end
      RBAC-->>HTTP Server: Ok
      HTTP Server-->>Client: 2xx Created
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The current description does not follow the repository’s template: it lacks the “Fixes #XXXX” section (or removal of that placeholder), the required “### Description” heading with goal, rationale, and key changes, as well as the checklist for testing, comments, and documentation. Please update the description to include or remove the “Fixes #XXXX” line as appropriate, add a “### Description” section that outlines the PR’s goal, chosen solution, and key changes, and complete the checklist confirming testing, documentation, and comment coverage.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change—allowing user creation without assigning roles—and follows the conventional “type: summary” format, making the main change clear to reviewers.

Poem

I thump my paws: new routes align,
POST prints true in tidy line.
Empty roles? A gentle hop—
create the user, no need to stop.
If names collide, we now confess—
the username’s in the error dress.
Carrots up; the checks progress! 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (10)
src/handlers/http/modal/query_server.rs (1)

197-206: Doc fix matches actual route; minor comment inconsistency nearby

POST annotation now matches web::post(). In the same resource block, the delete comment says {userid} while the path segment is {username}; consider harmonizing to avoid confusion.

-                    // DELETE /user/{userid} => Delete a user
+                    // DELETE /user/{username} => Delete a user
src/handlers/http/modal/query/querier_rbac.rs (4)

51-55: Prefer typed JSON extractor over serde_json::Value

Using Option<web::Json<HashSet<String>>> avoids manual deserialization and yields consistent 400s on invalid payloads.

-pub async fn post_user(
-    username: web::Path<String>,
-    body: Option<web::Json<serde_json::Value>>,
-) -> Result<impl Responder, RBACError> {
+pub async fn post_user(
+    username: web::Path<String>,
+    body: Option<web::Json<HashSet<String>>>,
+) -> Result<impl Responder, RBACError> {
@@
-    let user_roles: HashSet<String> = if let Some(body) = body {
-        serde_json::from_value(body.into_inner())?
-    } else {
-        HashSet::new()
-    };
+    let user_roles: HashSet<String> = body.map(|b| b.into_inner()).unwrap_or_default();

57-66: Avoid repeated lock acquisitions; take the roles map once

roles() returns a read guard; grabbing it once is cheaper and clearer.

-    let mut non_existent_roles = Vec::new();
-    for role in &user_roles {
-        if !roles().contains_key(role) {
-            non_existent_roles.push(role.clone());
-        }
-    }
+    let mut non_existent_roles = Vec::new();
+    let roles_map = roles();
+    for role in &user_roles {
+        if !roles_map.contains_key(role) {
+            non_existent_roles.push(role.clone());
+        }
+    }

Note: elsewhere in this file, non_existant_roles is misspelled; consider standardizing to non_existent_roles for consistency.


66-86: Don’t hold UPDATE_LOCK across network/sync calls

The lock is held while calling sync_user_creation_with_ingestors (and the subsequent role add), which can block unrelated updates. Scope the lock strictly to metadata/in-memory mutations.

-    let _guard = UPDATE_LOCK.lock().await;
-    if Users.contains(&username)
+    // critical section: check & persist
+    {
+        let _guard = UPDATE_LOCK.lock().await;
+        if Users.contains(&username)
         || metadata
             .users
             .iter()
             .any(|user| matches!(&user.ty, UserType::Native(basic) if basic.username == username))
-    {
-        return Err(RBACError::UserExists(username));
-    }
-
-    let (user, password) = user::User::new_basic(username.clone());
-
-    metadata.users.push(user.clone());
-
-    put_metadata(&metadata).await?;
-    let created_role = user_roles.clone();
-    Users.put_user(user.clone());
+        {
+            return Err(RBACError::UserExists(username));
+        }
+        metadata.users.push(user.clone());
+        put_metadata(&metadata).await?;
+        Users.put_user(user.clone());
+    } // lock released before outbound calls

Optional: to avoid double-sync, consider populating initial roles in metadata before the first sync (or introduce an internal helper that updates roles without emitting a secondary “add” sync when invoked from user creation).


85-91: Initial role assignment path duplicates sync work

You sync creation with roles, then call add_roles_to_user (which syncs again). If idempotency isn’t guaranteed, this can be noisy. Either:

  • include roles in the created user before persisting and skip the add path, or
  • add a non-syncing internal role-attach helper for creation.
src/handlers/http/rbac.rs (5)

114-123: Avoid repeated role-map lock acquisitions.

roles() returns a read guard; calling it inside the loop re-acquires the lock repeatedly. Cache it once and compute missing roles idiomatically.

-    let mut non_existent_roles = Vec::new();
-    for role in &user_roles {
-        if !roles().contains_key(role) {
-            non_existent_roles.push(role.clone());
-        }
-    }
+    let roles_map = roles();
+    let non_existent_roles: Vec<String> = user_roles
+        .iter()
+        .filter(|r| !roles_map.contains_key(*r))
+        .cloned()
+        .collect();

124-129: Redundant condition; simplify for clarity.

Users.contains(&username) && Users.contains(&username) is equivalent to a single call and needlessly duplicates the check.

-    if Users.contains(&username) && Users.contains(&username)
+    if Users.contains(&username)
         || metadata.users.iter().any(|user| match &user.ty {
             UserType::Native(basic) => basic.username == username,
             UserType::OAuth(_) => false, // OAuth users should be created differently
         })

140-146: Don’t call an HTTP handler from another handler; extract a shared service.

This works but couples handler-to-handler and forces Path/Json wrapping. Prefer a small internal function (e.g., assign_roles_to_user(userid: &str, roles: &HashSet<String>) -> Result<(), RBACError>) that both endpoints call.


422-423: Consider HTTP 409 Conflict for “user exists”.

409 is more semantically accurate than 400 for duplicates.

-            Self::UserExists(_) => StatusCode::BAD_REQUEST,
+            Self::UserExists(_) => StatusCode::CONFLICT,

466-472: serde attribute likely incorrect; use rename_all.

#[serde(rename = "camelCase")] won’t camelCase fields; it renames the type. Use rename_all.

-#[serde(rename = "camelCase")]
+#[serde(rename_all = "camelCase")]
 pub struct RolesResponse {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5600b and b05a4d1.

📒 Files selected for processing (4)
  • src/handlers/http/modal/query/querier_rbac.rs (2 hunks)
  • src/handlers/http/modal/query_server.rs (1 hunks)
  • src/handlers/http/modal/server.rs (1 hunks)
  • src/handlers/http/rbac.rs (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/handlers/http/rbac.rs (3)
src/rbac/map.rs (1)
  • roles (74-80)
src/handlers/http/modal/query/querier_rbac.rs (1)
  • add_roles_to_user (158-209)
src/handlers/http/modal/ingest/ingestor_rbac.rs (1)
  • add_roles_to_user (78-118)
src/handlers/http/modal/query/querier_rbac.rs (3)
src/rbac/map.rs (1)
  • roles (74-80)
src/handlers/http/rbac.rs (2)
  • Users (71-71)
  • add_roles_to_user (255-304)
src/handlers/http/modal/ingest/ingestor_rbac.rs (1)
  • add_roles_to_user (78-118)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Build Default aarch64-apple-darwin
  • GitHub Check: Build Default x86_64-apple-darwin
  • GitHub Check: Build Default x86_64-pc-windows-msvc
  • GitHub Check: Build Default aarch64-unknown-linux-gnu
  • GitHub Check: Build Default x86_64-unknown-linux-gnu
  • GitHub Check: Build Kafka x86_64-unknown-linux-gnu
  • GitHub Check: Build Kafka aarch64-apple-darwin
  • 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 (5)
src/handlers/http/modal/server.rs (1)

643-649: Doc fix matches actual route; LGTM

The POST annotation now aligns with the implemented web::post() route.

src/handlers/http/modal/query/querier_rbac.rs (1)

73-74: No further updates required for UserExists error variant
The UserExists(String) variant is already covered in the ResponseError impl (Self::UserExists(_) => StatusCode::BAD_REQUEST), and no other pattern matches rely on a payload-less form.

src/handlers/http/rbac.rs (3)

111-112: Allowing empty body = zero roles is correct; document the new POST contract.

Treating a missing body as an empty role set aligns with the PR goal. Please update API docs/examples to show:

  • POST with no body creates a user with zero roles.
  • POST with [] behaves the same.

383-385: Public error shape change — call out in release notes.

RBACError::UserExists(String) changes the public error text/JSON. Flag this in changelog to help clients parsing errors.


130-131: All UserExists variants now carry the payload correctly
Both constructor sites (rbac.rs and querier_rbac.rs) return Err(RBACError::UserExists(username)), and no unit-style match arms remain.

@nikhilsinhaparseable nikhilsinhaparseable merged commit 5e89972 into parseablehq:main Sep 10, 2025
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant