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** - - - Okta 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`) - - - Application Groups claim configuration - - -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 - - - Application Sign On configuration - - -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): Assign custom role to user @@ -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**. + + +Okta API tokens screen + + +1. Click on "Create token" + + + Create token dialog in Okta + + +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" + /> +
+ ) +} diff --git a/ui/app/workspace/dashboard/components/charts/modelFilterSelect.tsx b/ui/app/workspace/dashboard/components/charts/modelFilterSelect.tsx index fe8d459ac1..c605182525 100644 --- a/ui/app/workspace/dashboard/components/charts/modelFilterSelect.tsx +++ b/ui/app/workspace/dashboard/components/charts/modelFilterSelect.tsx @@ -10,10 +10,16 @@ interface ModelFilterSelectProps { "data-testid"?: string; } -export function ModelFilterSelect({ models, selectedModel, onModelChange, placeholder = "All Models", "data-testid": testId }: ModelFilterSelectProps) { +export function ModelFilterSelect({ + models, + selectedModel, + onModelChange, + placeholder = "All Models", + "data-testid": testId, +}: ModelFilterSelectProps) { return (