diff --git a/docs/enterprise/setting-up-okta.mdx b/docs/enterprise/setting-up-okta.mdx
index 7b6f25f0bc..6a097e2b96 100644
--- a/docs/enterprise/setting-up-okta.mdx
+++ b/docs/enterprise/setting-up-okta.mdx
@@ -13,7 +13,7 @@ This guide walks you through configuring Okta as your identity provider for Bifr
- An Okta organization with admin access
- Bifrost Enterprise deployed and accessible
- The redirect URI for your Bifrost instance (e.g., `https://your-bifrost-domain.com/login`)
-
+- Ensure you have created all the [roles in Bifrost](/enterprise/rbac) that you are aiming to map to with Okta.
---
## Step 1: Create an OIDC Application
@@ -71,39 +71,12 @@ Configure the following settings for your application:
---
-## Step 3: Configure Authorization Server (optional)
+## Step 3: Create Custom Role Attribute
-The default authorization server (`/oauth2/default`) is available to all Okta plans and **supports custom claims**, including role claims. The API Access Management paid add-on is only required to create additional custom authorization servers beyond the default.
+You can use both roles and/or groups for assigning roles to users. You can learn more about [RBAC](/enterprise/rbac) docs. Roles takes precedence over groups in role assignment.
-Bifrost uses Okta's Authorization Server to issue tokens. You have three options:
-
-1. **Use `/oauth2/default` with role claims (recommended)** — Complete Steps 4-7 to configure custom role claims on the default authorization server. This enables automatic RBAC synchronization.
-
-2. **Use `/oauth2/default` without role claims** — Skip Steps 4-7. The first user to sign in automatically receives the Admin role and can manage RBAC for all subsequent users through the Bifrost dashboard.
-
-3. **Skip Step 3 entirely** — Authorization is not configured through Okta. You'll need an alternative authentication mechanism.
-
-### Configuring the Authorization Server
-
-1. Navigate to **Security** → **API**
-2. Click on **Authorization Servers**
-
-
-
-
-
-3. Note the **Issuer URI** for your authorization server (e.g., `https://your-domain.okta.com/oauth2/default`)
-
-
-The Issuer URI is used as the `issuerUrl` in your Bifrost configuration. Make sure to use the full URL including `/oauth2/default` (or your custom authorization server path).
-
-
----
-
-## Step 4: Create Custom Role Attribute
-
To map Okta users to Bifrost roles (Admin, Developer, Viewer), you need to create a custom attribute.
1. Navigate to **Directory** → **Profile Editor**
@@ -133,7 +106,7 @@ To map Okta users to Bifrost roles (Admin, Developer, Viewer), you need to creat
---
-## Step 5: Add Role Claim to Tokens
+## Step 4: Add Role Claim to Tokens
Configure the authorization server to include the role in the access token.
@@ -164,11 +137,11 @@ If you named your custom attribute differently, update the Value expression acco
---
-## Step 6: Configure Groups for Team and Role Synchronization
+## Step 5: Configure Groups
Bifrost can automatically sync Okta groups for two purposes:
- **Team synchronization** — Groups are synced as Bifrost teams
-- **Role mapping** — Groups can be mapped to Bifrost roles (Admin, Developer, Viewer) using Group-to-Role Mappings in the Bifrost UI
+- **Role mapping** — Groups can be mapped to Bifrost roles (Admin, Developer, Viewer) using Group-to-Role Mappings in the Bifrost UI.
### Create Groups in Okta
@@ -191,31 +164,6 @@ Use a consistent naming convention for your groups. This makes it easier to conf
### Add Groups Claim to Tokens
-You have two options for configuring the groups claim. Choose the one that best fits your Okta plan and requirements.
-
-#### Option A: Using App-Level Groups Claim (All Okta Plans)
-
-This approach configures the groups claim directly in your application's settings and works with all Okta plans, including free tiers.
-
-1. Navigate to your application's **Sign On** tab
-2. Scroll down to the **OpenID Connect ID Token** section
-3. Click **Edit** to modify the settings
-4. Configure the **Groups claim filter**:
- - **Groups claim type**: Filter
- - **Groups claim filter**: Set a claim name (e.g., `groups`) and filter condition (e.g., "Starts with" `bifrost-staging`)
-
-
-
-
-
-5. Click **Save**
-
-
-The filter ensures only relevant groups are included in the token. Adjust the filter condition based on your group naming convention.
-
-
-#### Option B: Using Authorization Server Groups Claim
-
This approach adds the groups claim through your authorization server, providing more flexibility for complex configurations.
1. Navigate to **Security** → **API** → **Authorization Servers**
@@ -235,25 +183,9 @@ Configure the groups claim:
5. Click **Create**
-You can also configure an additional groups claim in the application's Sign On settings:
-
-1. Navigate to your application's **Sign On** tab
-
-
-
-
-
-2. Under **OpenID Connect ID Token**, configure:
- - **Groups claim type**: Expression
- - **Groups claim expression**: `Arrays.flatten(Groups.startsWith("OKTA", "bifrost", 100))`
-
-
-Adjust the group filter expression based on your naming convention. The example above includes groups starting with "bifrost".
-
-
---
-## Step 7: Assign Users to the Application
+## Step 6: Assign Users to the Application
1. Navigate to your application's **Assignments** tab
@@ -263,7 +195,9 @@ Adjust the group filter expression based on your naming convention. The example
2. Click **Assign** → **Assign to People** or **Assign to Groups**
-3. For each user, set their **bifrostRole**:
+### For Assigning Roles
+
+For each user, set their **bifrostRole** (if you are planning to do role-level mapping):
@@ -277,6 +211,22 @@ Role claims are available only when you configure custom claims on your authoriz
---
+## Step 7: Create API token for bulk user and team sync
+
+To create an API token, navigate to **Security** → **API** → **Tokens**.
+
+
+
+
+
+1. Click on "Create token"
+
+
+
+
+
+2. Copy token to be used in the next step.
+
## Step 8: Configure Bifrost
Now configure Bifrost to use Okta as the identity provider.
@@ -297,9 +247,9 @@ Now configure Bifrost to use Okta as the identity provider.
4. Toggle **Enabled** to activate the provider
5. Click **Save Configuration**
-### Group-to-Role Mappings (Optional)
+### Group-to-Role Mappings
-If you configured groups in Okta (Step 6), you can map Okta group names directly to Bifrost roles. This is an alternative to using custom role claims (Steps 4-5) and works with all Okta plans.
+If you configured groups in Okta (Step 5), you can map Okta group names directly to Bifrost roles. This is an alternative to using custom role claims (Steps 3-4) and works with all Okta plans.
1. In the User Provisioning configuration, scroll down to **Group-to-Role Mappings**
2. Click **Add Mapping**
diff --git a/docs/media/user-provisioning/okta-api-token-created.png b/docs/media/user-provisioning/okta-api-token-created.png
new file mode 100644
index 0000000000..e442519f8f
Binary files /dev/null and b/docs/media/user-provisioning/okta-api-token-created.png differ
diff --git a/docs/media/user-provisioning/okta-create-token-form.png b/docs/media/user-provisioning/okta-create-token-form.png
new file mode 100644
index 0000000000..2888d28da7
Binary files /dev/null and b/docs/media/user-provisioning/okta-create-token-form.png differ
diff --git a/docs/media/user-provisioning/okta-tokens-screen.png b/docs/media/user-provisioning/okta-tokens-screen.png
new file mode 100644
index 0000000000..6530a8a6d7
Binary files /dev/null and b/docs/media/user-provisioning/okta-tokens-screen.png differ
diff --git a/framework/configstore/rdb.go b/framework/configstore/rdb.go
index fac36dd528..f3b5d45b68 100644
--- a/framework/configstore/rdb.go
+++ b/framework/configstore/rdb.go
@@ -35,6 +35,7 @@ func getWeight(w *float64) float64 {
return *w
}
+// schemaKeyFromTableKey converts a database key to a schema key.
func schemaKeyFromTableKey(dbKey tables.TableKey) schemas.Key {
return schemas.Key{
ID: dbKey.KeyID,
@@ -59,6 +60,7 @@ func schemaKeyFromTableKey(dbKey tables.TableKey) schemas.Key {
}
}
+// tableKeyFromSchemaKey converts a schema key to a database key.
func tableKeyFromSchemaKey(provider tables.TableProvider, key schemas.Key) (tables.TableKey, error) {
dbKey := tables.TableKey{
Provider: provider.Name,
@@ -175,13 +177,10 @@ func (s *RDBConfigStore) parseGormError(err error) error {
if err == nil {
return nil
}
-
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrNotFound
}
-
errMsg := err.Error()
-
// Check for unique constraint violations
// SQLite format: "UNIQUE constraint failed: table_name.column_name"
// PostgreSQL format: "ERROR: duplicate key value violates unique constraint"
@@ -914,7 +913,6 @@ func (s *RDBConfigStore) CreateProviderKey(ctx context.Context, provider schemas
} else {
txDB = s.db
}
-
var dbProvider tables.TableProvider
if err := txDB.WithContext(ctx).Where("name = ?", string(provider)).First(&dbProvider).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -922,16 +920,13 @@ func (s *RDBConfigStore) CreateProviderKey(ctx context.Context, provider schemas
}
return err
}
-
dbKey, err := tableKeyFromSchemaKey(dbProvider, key)
if err != nil {
return err
}
-
if err := txDB.WithContext(ctx).Create(&dbKey).Error; err != nil {
return s.parseGormError(err)
}
-
return nil
}
@@ -1859,10 +1854,11 @@ func preloadCustomerRelations(db *gorm.DB, prefix string) *gorm.DB {
// preloadVirtualKeyBaseRelations preloads the base relationships for a virtual key.
func preloadVirtualKeyBaseRelations(db *gorm.DB) *gorm.DB {
- db = db.Preload("Team").Preload("Team.Customer")
- db = db.Preload("Customer")
return db.
- Preload("Budget").
+ Preload("Team").
+ Preload("Team.Customer").
+ Preload("Customer").
+ Preload("Budgets").
Preload("RateLimit").
Preload("ProviderConfigs").
Preload("ProviderConfigs.Budgets").
@@ -3163,6 +3159,7 @@ func (s *RDBConfigStore) GetModelConfigs(ctx context.Context) ([]tables.TableMod
return modelConfigs, nil
}
+// GetModelConfigsPaginated retrieves model configs with pagination, filtering, and search support.
func (s *RDBConfigStore) GetModelConfigsPaginated(ctx context.Context, params ModelConfigsQueryParams) ([]tables.TableModelConfig, int64, error) {
baseQuery := s.db.WithContext(ctx).Model(&tables.TableModelConfig{})
diff --git a/plugins/governance/test_utils.go b/plugins/governance/test_utils.go
index 25c4745285..b7ad6dbad3 100644
--- a/plugins/governance/test_utils.go
+++ b/plugins/governance/test_utils.go
@@ -15,7 +15,6 @@ import (
configstoreTables "github.com/maximhq/bifrost/framework/configstore/tables"
"github.com/maximhq/bifrost/framework/modelcatalog"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
)
// MockLogger implements schemas.Logger for testing
@@ -244,15 +243,6 @@ func assertRateLimitInfo(t *testing.T, result *EvaluationResult) {
assert.NotNil(t, result.RateLimitInfo, "RateLimitInfo should be present in result")
}
-func requireNoError(t *testing.T, err error, msg string) {
- t.Helper()
- require.NoError(t, err, msg)
-}
-
-func requireError(t *testing.T, err error, msg string) {
- t.Helper()
- require.Error(t, err, msg)
-}
func buildModelConfig(id, modelName string, provider *string, budget *configstoreTables.TableBudget, rateLimit *configstoreTables.TableRateLimit) *configstoreTables.TableModelConfig {
mc := &configstoreTables.TableModelConfig{
diff --git a/ui/app/_fallbacks/enterprise/components/user-groups/businessUnitsView.tsx b/ui/app/_fallbacks/enterprise/components/user-groups/businessUnitsView.tsx
new file mode 100644
index 0000000000..aa6c08759e
--- /dev/null
+++ b/ui/app/_fallbacks/enterprise/components/user-groups/businessUnitsView.tsx
@@ -0,0 +1,16 @@
+import { Users } from "lucide-react";
+import ContactUsView from "../views/contactUsView";
+
+export function BusinessUnitsView() {
+ return (
+
+ }
+ title="Unlock advanced governance"
+ description="Manage users, business units with our enterprise-grade governance. This feature is part of the Bifrost enterprise license."
+ readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
+ />
+
+ );
+}
diff --git a/ui/app/_fallbacks/enterprise/components/user-groups/teamsView.tsx b/ui/app/_fallbacks/enterprise/components/user-groups/teamsView.tsx
new file mode 100644
index 0000000000..90c1ecab37
--- /dev/null
+++ b/ui/app/_fallbacks/enterprise/components/user-groups/teamsView.tsx
@@ -0,0 +1,16 @@
+import { Users } from "lucide-react";
+import ContactUsView from "../views/contactUsView";
+
+export function TeamsView() {
+ return (
+
+ }
+ title="Unlock teams governance"
+ description="Manage teams, sync from your identity provider, and control access with enterprise-grade governance. This feature is part of the Bifrost enterprise license."
+ readmeLink="https://docs.getbifrost.ai/enterprise/advanced-governance"
+ />
+