1
0
mirror of https://github.com/bitwarden/server synced 2026-02-18 10:23:27 +00:00

[PM-31978] Expand Organization Ability documentation (#6970)

This commit is contained in:
Thomas Rittson
2026-02-18 08:54:53 +10:00
committed by GitHub
parent e660bb3577
commit 24b988508c

View File

@@ -1,4 +1,4 @@
# Organization Ability Flags
# Organization ability flags
## Overview
@@ -7,11 +7,11 @@ while Event Logs are available to Teams and Enterprise plans. When developing fe
control, we use **Organization Ability Flags** (or simply _abilities_) — explicit boolean properties on the Organization
entity that indicate whether an organization can use a specific feature.
## The Rule
## The rule
**Never check plan types to control feature access.** Always use a dedicated ability flag on the Organization entity.
### ❌ Don't Do This
### ❌ Don't do this
```csharp
// Checking plan type directly
@@ -23,7 +23,7 @@ if (organization.PlanType == PlanType.Enterprise ||
}
```
### ❌ Don't Do This
### ❌ Don't do this
```csharp
// Piggybacking off another feature's ability
@@ -33,17 +33,18 @@ if (organization.PlanType == PlanType.Enterprise && organization.UseEvents)
}
```
### ✅ Do This Instead
### ✅ Do this instead
```csharp
// Check the explicit ability flag
if (organization.UseEvents)
if (!organization.UseEvents)
{
// allow UseEvents feature...
throw new BadRequestException("Your organization does not have access to this feature.");
}
// proceed with feature logic...
```
## Why This Pattern Matters
## Why this pattern matters
Using explicit ability flags instead of plan type checks provides several benefits:
@@ -53,11 +54,11 @@ Using explicit ability flags instead of plan type checks provides several benefi
creation/upgrade. No need to hunt through the codebase for scattered plan type checks.
3. **Flexibility** — Abilities can be set independently of plan type, enabling:
- Early access programs for features not yet tied to a plan
- Trial access to help customers evaluate a feature before upgrading
- Custom arrangements for specific customers
- Custom arrangements for specific customers (can be manually toggled in Bitwarden Portal)
- A/B testing of features across different cohorts
- Gating high-risk features behind internal support teams (e.g., Key Connector)
4. **Safe Refactoring** — When plans change (e.g., adding a new plan tier, renaming plans, or moving features between
tiers), we only update the ability assignment logic—not every place the feature is used.
@@ -65,9 +66,34 @@ Using explicit ability flags instead of plan type checks provides several benefi
5. **Graceful Downgrades** — When an organization downgrades, we update their abilities. All feature checks
automatically respect the new access level.
## How It Works
6. **Semantic Code** — The code clearly expresses what capability is being checked, making it more maintainable.
### Ability Assignment at Signup/Upgrade
## Organization abilities and other features
Organization abilities work alongside other access control mechanisms. Understanding the differences helps you choose the right tool:
| | **Organization abilities** (this document) | **Feature flags** | **Enterprise policies** |
|-------------------|----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------|
| **Purpose** | Control whether an organization has **access** to a feature | Control feature **rollout** and act as a killswitch if necessary | Control **behavior** of features the organization already has access to |
| **Set by** | Subscription plan (automatically) or internal support teams (manual override via Bitwarden Portal) | Engineering teams | Organization admins and owners |
| **Lifecycle** | Permanent - part of the core product | Temporary - removed once feature is stable | Permanent - part of the core product |
| **Scope** | Per organization | Global or targeted | Per organization |
| **Toggle method** | Bitwarden Portal (single) or data migration (bulk) | LaunchDarkly | In-product via Admin Console |
| **Examples** | Can the org use SSO? Can they use SCIM? Can they use Events? | Is the new API available? Is the redesigned UI enabled? | Require 2FA for all users, enforce password complexity |
### When to use which?
**Use an organization ability** when the feature will be permanently gated behind a subscription tier or our support teams.
**Use a feature flag** when you need to control the release of a new feature.
**Use a policy** when you're adding configurable rules to a feature the organization can already access.
**Use multiple together** when appropriate. For example, a new enterprise feature might use all three: a feature flag to control initial rollout, an organization ability to restrict it to Enterprise plans, and a policy to let admins configure enforcement rules.
## How it works
### Ability assignment at signup/upgrade
When an organization is created or changes plans, the ability flags are set based on the plan's capabilities:
@@ -81,48 +107,225 @@ organization.UseEvents = plan.HasEvents;
// ... etc
```
### Modifying Abilities for Existing Organizations
### Accessing abilities in code
To change abilities for existing organizations (e.g., rolling out a feature to a new plan tier), create a database
migration that updates the relevant flag:
**Server-side:**
- If you already have the full `Organization` object in scope, use it directly: `organization.UseMyFeature`
- If not, use the in-memory cache to avoid hitting the database:
`IApplicationCacheService.GetOrganizationAbilityAsync(orgId)`
- This returns an `OrganizationAbility` object - a simplified, cached representation of the ability flags
- Note: some older flags may be missing from `OrganizationAbility` but can be added if needed
**Client-side:**
- Get the organization object from `OrganizationService`, then use it directly: `organization.useMyFeature`
### Manual override via Bitwarden Portal
Organization abilities can be manually toggled for specific customers via the Bitwarden Portal → Organizations page.
This is useful for custom arrangements, early access, or internal testing.
## Adding a new ability
When developing a new plan-gated feature, follow these steps. We use `MyFeature` as a placeholder for your feature name
(e.g., `UseEvents`).
### 1. Update core entities
- `src/Core/AdminConsole/Entities/Organization.cs` — Add `UseMyFeature` boolean property
- `src/Core/AdminConsole/OrganizationFeatures/OrganizationAbility/OrganizationAbility.cs` — Add to ability object
### 2. Database changes (MSSQL)
Add a new `UseMyFeature` column to the Organization table:
**Files to modify:**
- `src/Sql/dbo/Tables/Organization.sql` — Add column with `NOT NULL` constraint and default of `0` (false) for EDD
backward compatibility
**Stored procedures to update:**
- `src/Sql/dbo/Stored Procedures/Organization_Create.sql`
- `src/Sql/dbo/Stored Procedures/Organization_Update.sql`
- `src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql`
**Views to update (add the new column):**
- `src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql`
- `src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql`
- `src/Sql/dbo/Views/OrganizationView.sql`
**Views to refresh (use `sp_refreshview`):**
After schema changes, the following views may need to be refreshed even though they don't explicitly include the new
column:
- `src/Sql/dbo/Views/OrganizationCipherDetailsCollectionsView.sql`
- `src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql`
**Create a migration script** for these database changes.
### 3. Entity Framework changes
EF is primarily used for self-host. Implementations must be kept consistent.
**Generate EF migrations** for the new column.
**Update queries and initialization code:**
- `src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs`
- Update `GetManyAbilitiesAsync()` to initialize the new property
- `src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs`
- Update the integration test:
`test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs`
- `src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs`
### 4. Data migrations for existing organizations
If your feature should be enabled for existing organizations on certain plan types, create data migrations to set the
ability flag:
**MSSQL migration:**
```sql
-- Example: Enable UseEvents for all Teams organizations
-- Example: Enable UseMyFeature for all Enterprise organizations
-- Check src/Core/Billing/Enums/PlanType.cs for current values
UPDATE [dbo].[Organization]
SET UseEvents = 1
WHERE PlanType IN (17, 18) -- TeamsMonthly = 17, TeamsAnnually = 18
SET UseMyFeature = 1
WHERE PlanType IN (4, 5, 10, 11, 14, 15, 19, 20) -- All Enterprise plan types (2019, 2020, 2023, current)
```
Then update the plan-to-ability assignment code so new organizations get the correct value.
**EF migration:**
## Adding a New Ability
Create a corresponding data migration for EF databases used by self-hosted instances.
When developing a new plan-gated feature:
### 5. Server code changes
1. **Add the ability to the Organization and OrganizationAbility entities** — Create a `Use[FeatureName]` boolean
property.
Update related models and mapping code so models receive the new value.
2. **Add a database migration** — Add the new column to the Organization table.
**Response models:**
3. **Update plan definitions** — Add a corresponding `Has[FeatureName]` property to the Plan model and configure which
plans include it.
- `src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs`
- `src/Api/AdminConsole/Models/Response/BaseProfileOrganizationResponseModel.cs`
4. **Update organization creation/upgrade logic** — Ensure the ability is set based on the plan.
**Data models:**
5. **Update the organization license claims** (if applicable) - to make the feature available on self-hosted instances.
- `src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs`
- `src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs`
- `src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs`
- `src/Core/AdminConsole/Models/Data/IProfileOrganizationDetails.cs`
6. **Implement checks throughout client and server** — Use the ability consistently everywhere the feature is accessed.
- Clients: get the organization object from `OrganizationService`.
- Server: if you already have the full `Organization` object in scope, you can use it directly. If not, use the
`IApplicationCacheService` to retrieve the `OrganizationAbility`, which is a simplified, cached representation
of the organization ability flags. Note that some older flags may be missing from `OrganizationAbility` but
can be added if needed.
**Plan definition and signup logic:**
## Existing Abilities
If your feature should be automatically enabled based on plan type at signup (e.g., SSO for Enterprise plans), you'll
need to:
1. Work with the Billing Team to add a `HasMyFeature` property to the Plan model and configure which plans include it
2. Update `src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs` to map
`plan.HasMyFeature` to `organization.UseMyFeature`
**Note:** This step is not required if your feature is enabled manually via the Admin Portal.
### 6. Client changes
**TypeScript models to update:**
- `libs/common/src/admin-console/models/response/profile-organization.response.ts`
- `libs/common/src/admin-console/models/response/organization.response.ts`
- `libs/common/src/admin-console/models/domain/organization.ts`
- `libs/common/src/admin-console/models/data/organization.data.ts`
- Update tests: `libs/common/src/admin-console/models/data/organization.data.spec.ts`
### 7. Bitwarden Portal changes
For manual override capability in the admin portal:
- `src/Admin/AdminConsole/Models/OrganizationEditModel.cs` — Map the ability from the organization entity
- `src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml` — Add checkbox for the new ability
- `src/Admin/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml` — Add the new ability to the
`togglePlanFeatures()` function so it's automatically set when a plan type is selected
- `src/Admin/AdminConsole/Controllers/OrganizationsController.cs` — Update `UpdateOrganization()` method mapping
### 8. Self-host licensing
> ⚠️ **WARNING:** Mistakes in organization license changes can disable the entire organization for self-hosted
> customers!
> Double-check your work and ask for help if unsure.
>
> **Note:** New properties must be added to both the `OrganizationLicense` class and the claims-based system.
**Update OrganizationLicense:**
- `src/Core/Billing/Organizations/Models/OrganizationLicense.cs`
- Add the new property to the class
- `VerifyData()` — Add claims validation
- `GetDataBytes()` — Add the new property to the ignored fields section (below the comment
`// any new fields added need to be added here so that they're ignored`)
**Add property to Organization entity mapper:**
- `src/Core/AdminConsole/Entities/Organization.cs` — Add the new property to the `UpdateFromLicense()` method
**Add claims for the new feature:**
- `src/Core/Billing/Licenses/LicenseConstants.cs` — Add constant for the new ability in `OrganizationLicenseConstants`
- `src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs`
**Update license command:**
Map your feature property from the claim to the organization when creating or updating from the license file:
- `src/Core/AdminConsole/Services/OrganizationFactory.cs`
- `src/Core/Billing/Organizations/Commands/UpdateOrganizationLicenseCommand.cs`
**Update tests:**
- `test/Core.Test/Billing/Organizations/Commands/UpdateOrganizationLicenseCommandTests.cs` - add the new property to
`UpdateLicenseAsync_WithClaimsPrincipal_ExtractsAllPropertiesFromClaims` test
> **Tip:** Running tests in `UpdateOrganizationLicenseCommandTests.cs` will help identify any missing changes.
> Test failures will guide you to all areas that need updates.
### 9. Implement business logic checks
In your feature's business logic, check the ability flag:
```csharp
// Retrieve the organization ability (uses cache, avoids DB hit)
var orgAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId);
if (!orgAbility.UseMyFeature)
{
throw new BadRequestException("Your organization's plan does not support this feature.");
}
// Proceed with feature logic...
```
As explained above, organization abilities work alongside feature flags — they don't replace them.
For new features, you'll typically want both:
```csharp
// Check feature flag first (controls rollout)
if (!_featureService.IsEnabled(FeatureFlagKeys.MyFeature))
{
throw new BadRequestException("This feature is not available.");
}
// Then check organization ability (controls plan-based access)
if (!orgAbility.UseMyFeature)
{
throw new BadRequestException("Your organization's plan does not support this feature.");
}
```
## Existing abilities
For reference, here are some current organization ability flags (not a complete list):
| Ability | Description | Plans |
| Ability | Description | Typical Plans |
|--------------------------|-------------------------------|-------------------|
| `UseGroups` | Group-based collection access | Teams, Enterprise |
| `UseDirectory` | Directory Connector sync | Teams, Enterprise |