diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index 2e626c8c09..2d5680c4f1 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -251,10 +251,17 @@ func TestPostChats(t *testing.T) { _ = createChatModelConfig(t, client) // Member without agents-access should be denied. - memberClientRaw, _ := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) + memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) - _, err := memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ + // Strip the auto-assigned agents-access role to test + // the denied case. + _, err := client.Client.UpdateUserRoles(ctx, member.Username, codersdk.UpdateRoles{ + Roles: []string{}, + }) + require.NoError(t, err) + + _, err = memberClient.CreateChat(ctx, codersdk.CreateChatRequest{ Content: []codersdk.ChatInputPart{ { Type: codersdk.ChatInputPartTypeText, @@ -264,7 +271,6 @@ func TestPostChats(t *testing.T) { }) requireSDKError(t, err, http.StatusForbidden) }) - t.Run("HidesSystemPromptMessages", func(t *testing.T) { t.Parallel() @@ -628,7 +634,15 @@ func TestListChats(t *testing.T) { // returning empty because no chats exist. memberClientRaw, member := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID) memberClient := codersdk.NewExperimentalClient(memberClientRaw) - _, err := db.InsertChat(dbauthz.AsSystemRestricted(ctx), database.InsertChatParams{ + + // Strip the auto-assigned agents-access role to test + // the denied case. + _, err := client.Client.UpdateUserRoles(ctx, member.Username, codersdk.UpdateRoles{ + Roles: []string{}, + }) + require.NoError(t, err) + + _, err = db.InsertChat(dbauthz.AsSystemRestricted(ctx), database.InsertChatParams{ Status: database.ChatStatusWaiting, OwnerID: member.ID, LastModelConfigID: modelConfig.ID, diff --git a/coderd/users.go b/coderd/users.go index cfe0411bb2..29d4002c17 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -1619,6 +1619,18 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create rbacRoles = req.RBACRoles } + // When the agents experiment is enabled, auto-assign the + // agents-access role so new users can use Coder Agents + // without manual admin intervention. Skip this for OIDC + // users when site role sync is enabled, because the sync + // will overwrite roles on every login anyway — those + // admins should use --oidc-user-role-default instead. + if api.Experiments.Enabled(codersdk.ExperimentAgents) && + !(req.LoginType == database.LoginTypeOIDC && api.IDPSync.SiteRoleSyncEnabled()) && + !slices.Contains(rbacRoles, codersdk.RoleAgentsAccess) { + rbacRoles = append(rbacRoles, codersdk.RoleAgentsAccess) + } + var user database.User err := store.InTx(func(tx database.Store) error { orgRoles := make([]string, 0) diff --git a/coderd/users_test.go b/coderd/users_test.go index fd0453f5cd..4d1463d148 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -758,6 +758,35 @@ func TestPostUsers(t *testing.T) { assert.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0]) }) + // CreateWithAgentsExperiment verifies that new users + // are auto-assigned the agents-access role when the + // experiment is enabled. The experiment-disabled case + // is implicitly covered by TestInitialRoles, which + // asserts exactly [owner] with no experiment — it + // would fail if agents-access leaked through. + t.Run("CreateWithAgentsExperiment", func(t *testing.T) { + t.Parallel() + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{string(codersdk.ExperimentAgents)} + client := coderdtest.New(t, &coderdtest.Options{DeploymentValues: dv}) + firstUser := coderdtest.CreateFirstUser(t, client) + + ctx := testutil.Context(t, testutil.WaitLong) + + user, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + OrganizationIDs: []uuid.UUID{firstUser.OrganizationID}, + Email: "another@user.org", + Username: "someone-else", + Password: "SomeSecurePassword!", + }) + require.NoError(t, err) + + roles, err := client.UserRoles(ctx, user.Username) + require.NoError(t, err) + require.Contains(t, roles.Roles, codersdk.RoleAgentsAccess, + "new user should have agents-access role when agents experiment is enabled") + }) + t.Run("CreateWithStatus", func(t *testing.T) { t.Parallel() auditor := audit.NewMock() diff --git a/docs/ai-coder/agents/early-access.md b/docs/ai-coder/agents/early-access.md index 8a0fa419bb..ed39d4b206 100644 --- a/docs/ai-coder/agents/early-access.md +++ b/docs/ai-coder/agents/early-access.md @@ -65,9 +65,12 @@ Once the server restarts with the experiment enabled: 1. Navigate to the **Agents** page in the Coder dashboard. 1. Open **Admin** settings and configure at least one LLM provider and model. See [Models](./models.md) for detailed setup instructions. -1. Grant the **Coder Agents User** role to users who need to create chats. - Go to **Admin** > **Users**, click the roles icon next to each user, - and enable **Coder Agents User**. +1. Grant the **Coder Agents User** role to existing users who need to create + chats. New users receive the role automatically. For existing users, go to + **Admin** > **Users**, click the roles icon next to each user, and enable + **Coder Agents User**. See + [Grant Coder Agents User](./getting-started.md#step-3-grant-coder-agents-user) + for a bulk CLI option. 1. Developers can then start a new chat from the Agents page. ## Licensing and availability diff --git a/docs/ai-coder/agents/getting-started.md b/docs/ai-coder/agents/getting-started.md index e4e0536fe0..449eee721a 100644 --- a/docs/ai-coder/agents/getting-started.md +++ b/docs/ai-coder/agents/getting-started.md @@ -24,8 +24,9 @@ Before you begin, confirm the following: for the agent to select when provisioning workspaces. - **Admin access** to the Coder deployment for enabling the experiment and configuring providers. -- **Coder Agents User role** assigned to each user who needs to interact with Coder Agents. - Owners can assign this from **Admin** > **Users**. See +- **Coder Agents User role** is automatically assigned to new users when the + `agents` experiment is enabled. For existing users, owners can assign it from + **Admin** > **Users**. See [Grant Coder Agents User](#step-3-grant-coder-agents-user) below. ## Step 1: Enable the experiment @@ -74,15 +75,43 @@ Detailed instructions for each provider and model option are in the ## Step 3: Grant Coder Agents User -The **Coder Agents User** role controls which users can interact with Coder Agents. -Members do not have Coder Agents User by default. +The **Coder Agents User** role controls which users can interact with +Coder Agents. + +### New users + +When the `agents` experiment is enabled, new users are automatically +assigned the **Coder Agents User** role at account creation. No admin +action is required. + +### Existing users + +Users who were created before the experiment was enabled do not receive +the role automatically. Owners can assign it from the dashboard or in +bulk via the CLI. + +**Dashboard (individual):** 1. Go to **Admin** > **Users** in the Coder dashboard. 1. Click the roles icon next to the user you want to grant access to. 1. Enable the **Coder Agents User** role and save. -Repeat for each user who needs access. Owners always have full access -and do not need the role. +**CLI (bulk):** + +To grant the role to all active users at once: + +```sh +coder users list --status active -o json \ + | jq -r '.[].username' \ + | while read u; do + coder users edit-roles "$u" \ + --roles "$(coder users show "$u" -o json \ + | jq -r '[.roles[].name, "agents-access"] | unique | join(",")')" \ + --yes + done +``` + +Owners always have full access and do not need the role. > [!NOTE] > Users who created conversations before this role was introduced are diff --git a/enterprise/coderd/userauth_test.go b/enterprise/coderd/userauth_test.go index 4dde31c625..1f5e9d7723 100644 --- a/enterprise/coderd/userauth_test.go +++ b/enterprise/coderd/userauth_test.go @@ -291,6 +291,7 @@ func TestUserOIDC(t *testing.T) { }, DeploymentValues: func(dv *codersdk.DeploymentValues) { dv.OIDC.UserRoleField = "roles" + dv.Experiments = []string{string(codersdk.ExperimentAgents)} }, })