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:
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user