How Organizations, Collections, and Groups Work in Vaultwarden
Vaultwarden implements a hierarchical permission model where Organizations contain Collections of passwords, Groups aggregate users with shared permissions, and Memberships define individual access levels through a combination of direct assignments and group-based rights.
Vaultwarden, the open-source Bitwarden server implementation written in Rust, provides enterprise-grade password sharing through a sophisticated access control system. Understanding how organizations, collections, and groups work in Vaultwarden is essential for administrators managing team credentials. This article examines the data models in src/db/models/ and the API implementation to explain the permission flow from organization creation to individual cipher access.
The Core Data Model
Vaultwarden’s permission system rests on four interconnected entities defined in the database layer: Organization, Membership, Collection, and Group.
Organization Structure
An Organization represents the top-level container that groups members, collections, and groups. According to the source code in src/db/models/organization.rs (lines 26-30), the struct is defined as:
pub struct Organization {
pub uuid: OrganizationId,
pub name: String,
pub billing_email: String,
pub private_key: Option<String>,
pub public_key: Option<String>,
}
When an organization is created via the create_organization endpoint in src/api/core/organizations.rs, the system automatically provisions a default owner Membership and a default collection (lines 90-93 and 96-98). This ensures the creating user immediately has full administrative access to manage the organization’s vault.
Membership Types and Status
Memberships link individual users to organizations through the users_organizations table. Each membership carries a type defined by the MembershipType enum:
- Owner: Full administrative control, including billing and deletion rights
- Admin: Management access without ownership-specific functions
- User: Standard member with limited collection access
- Manager: Intermediate role with user management capabilities for assigned collections
A membership must also have a status of Confirmed before it grants any access rights. The type determines whether the member receives full access (owner/admin) or operates under limited collection/group constraints.
Collections as Permission Containers
Collections function as the primary unit of data sharing, containing ciphers (password entries) within an organization. Defined in src/db/models/collection.rs (lines 19-23):
pub struct Collection {
pub uuid: CollectionId,
pub org_uuid: OrganizationId,
pub name: String,
pub external_id: Option<String>,
}
Permissions are stored in two join tables:
users_collections→ Links a user directly to a collection withread_only,hide_passwords, andmanageboolean flagscollections_groups→ Links a Group to a collection with identical permission flags
Groups for Scalable Access Control
Groups provide a mechanism to grant identical collection permissions to multiple members simultaneously. The Group struct in src/db/models/group.rs (lines 16-22) includes a critical access_all field:
pub struct Group {
pub uuid: GroupId,
pub organizations_uuid: OrganizationId,
pub name: String,
pub access_all: bool,
pub external_id: Option<String>,
pub creation_date: NaiveDateTime,
pub revision_date: NaiveDateTime,
}
When access_all is set to true, every member assigned to that group receives full access to any collection linked to the organization. Group membership is tracked in the groups_users table, which associates GroupId values with MembershipId values.
Permission Evaluation Logic
Vaultwarden centralizes access control in the can_access_collection method within src/db/models/collection.rs (lines 40-46). This function implements a cascading permission check:
pub async fn can_access_collection(member: &Membership, col_id: &CollectionId, conn: &DbConn) -> bool {
member.has_status(MembershipStatus::Confirmed)
&& (member.has_full_access()
|| CollectionUser::has_access_to_collection_by_user(col_id, &member.user_uuid, conn).await
|| (CONFIG.org_groups_enabled()
&& (GroupUser::has_full_access_by_member(&member.org_uuid, &member.uuid, conn).await
|| GroupUser::has_access_to_collection_by_member(col_id, &member.uuid, conn).await)))
}
The evaluation follows this hierarchy:
- Verify the member’s status is Confirmed
- Check if the member has full access (owner or admin)
- Check for direct CollectionUser rights in
users_collections - If groups are enabled, verify group-based full access via
GroupUser::has_full_access_by_memberor specific collection access viaGroupUser::has_access_to_collection_by_member
The has_full_access_by_member function in src/db/models/group.rs (lines 33-49) queries the groups_users table joined with groups to count rows where access_all equals true for the given member:
pub async fn has_full_access_by_member(
org_uuid: &OrganizationId,
member_uuid: &MembershipId,
conn: &DbConn,
) -> bool {
db_run! { conn: {
groups_users::table
.inner_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid)))
.filter(groups::organizations_uuid.eq(org_uuid))
.filter(groups::access_all.eq(true))
.filter(groups_users::users_organizations_uuid.eq(member_uuid))
.count()
.first::<i64>(conn)
.unwrap_or(0) != 0
}}
}
Configuration and Feature Flags
Vaultwarden uses compile-time and runtime configuration to toggle organization features. The critical flag CONFIG.org_groups_enabled() determines whether group-related permission checks execute in the can_access_collection logic. Additional flags like CONFIG.org_events_enabled(), useCollections, and useGroups are reflected in the JSON payload returned by Organization::to_json (lines 96-98 in organization.rs), allowing clients to adapt their UI based on server capabilities.
API Endpoints for Managing Access
All CRUD operations for the permission hierarchy are implemented in src/api/core/organizations.rs. Key endpoints include:
POST /organizations→ Creates an organization, its owner membership, and a default collectionGET /organizations/:id/collections→ Lists collections belonging to an organizationPOST /organizations/:id/collections→ Creates a new collectionGET /organizations/:id/groups→ Lists available groupsPOST /organizations/:id/groups→ Creates a new groupPOST /organizations/:id/collections/:cid/groups→ Assigns a collection to a group (creates aCollectionGrouprow)POST /organizations/:id/collections/:cid/users→ Assigns a collection directly to a member (creates aCollectionUserrow)
These routes wire the data-model logic to the HTTP layer, ensuring that can_access_collection and related checks enforce permissions consistently across the API surface.
Practical Implementation Examples
Creating an Organization with Collections and Groups
The following Rust example demonstrates programmatic creation of the full hierarchy using Vaultwarden’s internal models:
use vaultwarden::db::models::{Organization, Collection, Group, Membership, MembershipType};
use vaultwarden::CONFIG;
// Create the organization
let org = Organization::new(
"Acme Corp".into(),
"admin@acme.com",
None, // private_key
None, // public_key
);
// Create the owner membership
let mut owner = Membership::new(user_uuid, org.uuid.clone(), None);
owner.atype = MembershipType::Owner as i32;
owner.status = vaultwarden::db::models::MembershipStatus::Confirmed as i32;
owner.access_all = true;
owner.akey = "random-key".into();
// Create a default collection
let collection = Collection::new(org.uuid.clone(), "Passwords".into(), None);
// Create a group with full access
let group = Group::new(org.uuid.clone(), "Admins".into(), true, None);
// Persist to database
org.save(&conn).await?;
owner.save(&conn).await?;
collection.save(&conn).await?;
group.save(&conn).await?;
Assigning User-Specific Collection Permissions
To grant a specific member manage rights to a collection via the REST API:
POST /organizations/{org_id}/collections/{collection_id}/users
Content-Type: application/json
{
"id": "<membership-id>",
"readOnly": false,
"hidePasswords": false,
"manage": true
}
The handler parses this payload into CollectionMembershipData (defined in src/api/core/organizations.rs lines 45-50) and creates a CollectionUser record, which subsequent calls to can_access_collection evaluate.
Granting Group-Based Collection Access
To link a collection to a group with specific permissions:
POST /organizations/{org_id}/collections/{collection_id}/groups
Content-Type: application/json
{
"id": "<group-id>",
"readOnly": true,
"hidePasswords": true,
"manage": false
}
This creates a CollectionGroup entry. When any member of the target group attempts to access the collection, GroupUser::has_access_to_collection_by_member validates their rights through the membership linkage in groups_users.
Checking Access Programmatically
Within protected endpoints, enforce permissions using the centralized check:
let member = Membership::find_by_uuid(&member_uuid, &conn).await.unwrap();
let can_view = Collection::can_access_collection(&member, &collection_uuid, &conn).await;
if !can_view {
return Err(ApiError::Forbidden);
}
Summary
- Organizations serve as the root container, automatically creating an owner membership and default collection upon instantiation in
src/db/models/organization.rs. - Collections contain encrypted items and act as the primary permission boundary, with access controlled through
users_collectionsandcollections_groupsjoin tables. - Groups enable scalable permission management via the
access_allflag and membership tracking ingroups_users, evaluated throughGroupUser::has_full_access_by_member. - Access evaluation follows a strict hierarchy in
Collection::can_access_collection, confirming membership status before checking full access, direct collection rights, or group-derived permissions. - Feature toggles like
CONFIG.org_groups_enabled()conditionally enable group logic, allowing administrators to disable complex group features if unnecessary.
Frequently Asked Questions
What is the difference between a Collection and a Group in Vaultwarden?
A Collection is a container for passwords (ciphers) that defines the actual data boundary for sharing, while a Group is a logical aggregation of users designed to simplify permission management. Collections store the actual encrypted vault items, whereas groups only store references to members and collection permissions. A single group can be granted access to multiple collections, and multiple groups can access the same collection, creating flexible role-based access control.
How does Vaultwarden determine if a user can access a specific password?
Vaultwarden executes the can_access_collection function in src/db/models/collection.rs, which requires the user’s membership status to be Confirmed. The function then checks four conditions in order: owner/admin full access, direct user-to-collection rights via CollectionUser, group-based full access via access_all, or specific group-to-collection rights via CollectionGroup. If any condition passes, access is granted.
What are the different membership types in a Vaultwarden organization?
Vaultwarden defines four membership types in the MembershipType enum: Owner (full control including billing and deletion), Admin (management without ownership-specific functions), Manager (user management for assigned collections), and User (limited access based on explicit collection or group assignments). Only Owners and Admins receive automatic full access to all organization collections unless explicitly restricted.
Can groups be disabled in Vaultwarden?
Yes. Groups are controlled by the CONFIG.org_groups_enabled() configuration flag. When disabled, the permission evaluation logic in can_access_collection skips all group-related checks, meaning only direct user-to-collection assignments and owner/admin full access determine permissions. This simplifies the permission model for smaller organizations that do not require complex group-based access control.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →