coder/enterprise/coderd/userauth_test.go

766 lines
23 KiB
Go

package coderd_test
import (
"context"
"net/http"
"regexp"
"testing"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
coderden "github.com/coder/coder/v2/enterprise/coderd"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)
// nolint:bodyclose
func TestUserOIDC(t *testing.T) {
t.Parallel()
t.Run("RoleSync", func(t *testing.T) {
t.Parallel()
// NoRoles is the "control group". It has claims with 0 roles
// assigned, and asserts that the user has no roles.
t.Run("NoRoles", func(t *testing.T) {
t.Parallel()
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
},
})
claims := jwt.MapClaims{
"email": "alice@coder.com",
}
// Login a new client that signs up
client, resp := runner.Login(t, claims)
require.Equal(t, http.StatusOK, resp.StatusCode)
// User should be in 0 groups.
runner.AssertRoles(t, "alice", []string{})
// Force a refresh, and assert nothing has changes
runner.ForceRefresh(t, client, claims)
runner.AssertRoles(t, "alice", []string{})
})
// Some IDPs (ADFS) send the "string" type vs "[]string" if only
// 1 role exists.
t.Run("SingleRoleString", func(t *testing.T) {
t.Parallel()
const oidcRoleName = "TemplateAuthor"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
cfg.UserRoleMapping = map[string][]string{
oidcRoleName: {rbac.RoleTemplateAdmin()},
}
},
})
// User starts with the owner role
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
// This is sent as a **string** intentionally instead
// of an array.
"roles": oidcRoleName,
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertRoles(t, "alice", []string{rbac.RoleTemplateAdmin()})
})
// A user has some roles, then on an oauth refresh will lose said
// roles from an updated claim.
t.Run("NewUserAndRemoveRolesOnRefresh", func(t *testing.T) {
// TODO: Implement new feature to update roles/groups on OIDC
// refresh tokens. https://github.com/coder/coder/issues/9312
t.Skip("Refreshing tokens does not update roles :(")
t.Parallel()
const oidcRoleName = "TemplateAuthor"
runner := setupOIDCTest(t, oidcTestConfig{
Userinfo: jwt.MapClaims{oidcRoleName: []string{rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin()}},
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
cfg.UserRoleMapping = map[string][]string{
oidcRoleName: {rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin()},
}
},
})
// User starts with the owner role
client, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
"roles": []string{"random", oidcRoleName, rbac.RoleOwner()},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertRoles(t, "alice", []string{rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(), rbac.RoleOwner()})
// Now refresh the oauth, and check the roles are removed.
// Force a refresh, and assert nothing has changes
runner.ForceRefresh(t, client, jwt.MapClaims{
"email": "alice@coder.com",
"roles": []string{"random"},
})
runner.AssertRoles(t, "alice", []string{})
})
// A user has some roles, then on another oauth login will lose said
// roles from an updated claim.
t.Run("NewUserAndRemoveRolesOnReAuth", func(t *testing.T) {
t.Parallel()
const oidcRoleName = "TemplateAuthor"
runner := setupOIDCTest(t, oidcTestConfig{
Userinfo: jwt.MapClaims{oidcRoleName: []string{rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin()}},
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
cfg.UserRoleMapping = map[string][]string{
oidcRoleName: {rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin()},
}
},
})
// User starts with the owner role
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
"roles": []string{"random", oidcRoleName, rbac.RoleOwner()},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertRoles(t, "alice", []string{rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin(), rbac.RoleOwner()})
// Now login with oauth again, and check the roles are removed.
_, resp = runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
"roles": []string{"random"},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertRoles(t, "alice", []string{})
})
// All manual role updates should fail when role sync is enabled.
t.Run("BlockAssignRoles", func(t *testing.T) {
t.Parallel()
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
},
})
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
"roles": []string{},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
// Try to manually update user roles, even though controlled by oidc
// role sync.
ctx := testutil.Context(t, testutil.WaitShort)
_, err := runner.AdminClient.UpdateUserRoles(ctx, "alice", codersdk.UpdateRoles{
Roles: []string{
rbac.RoleTemplateAdmin(),
},
})
require.Error(t, err)
require.ErrorContains(t, err, "Cannot modify roles for OIDC users when role sync is enabled.")
})
})
t.Run("Groups", func(t *testing.T) {
t.Parallel()
// Assigns does a simple test of assigning a user to a group based
// on the oidc claims.
t.Run("Assigns", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
const groupName = "bingbong"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
},
})
ctx := testutil.Context(t, testutil.WaitShort)
group, err := runner.AdminClient.CreateGroup(ctx, runner.AdminUser.OrganizationIDs[0], codersdk.CreateGroupRequest{
Name: groupName,
})
require.NoError(t, err)
require.Len(t, group.Members, 0)
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{groupName},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{groupName})
})
// Tests the group mapping feature.
t.Run("AssignsMapped", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
const oidcGroupName = "pingpong"
const coderGroupName = "bingbong"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
cfg.GroupMapping = map[string]string{oidcGroupName: coderGroupName}
},
})
ctx := testutil.Context(t, testutil.WaitShort)
group, err := runner.AdminClient.CreateGroup(ctx, runner.AdminUser.OrganizationIDs[0], codersdk.CreateGroupRequest{
Name: coderGroupName,
})
require.NoError(t, err)
require.Len(t, group.Members, 0)
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{oidcGroupName},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{coderGroupName})
})
// User is in a group, then on an oauth refresh will lose said
// group.
t.Run("AddThenRemoveOnRefresh", func(t *testing.T) {
t.Parallel()
// TODO: Implement new feature to update roles/groups on OIDC
// refresh tokens. https://github.com/coder/coder/issues/9312
t.Skip("Refreshing tokens does not update groups :(")
const groupClaim = "custom-groups"
const groupName = "bingbong"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
},
})
ctx := testutil.Context(t, testutil.WaitShort)
group, err := runner.AdminClient.CreateGroup(ctx, runner.AdminUser.OrganizationIDs[0], codersdk.CreateGroupRequest{
Name: groupName,
})
require.NoError(t, err)
require.Len(t, group.Members, 0)
client, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{groupName},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{groupName})
// Refresh without the group claim
runner.ForceRefresh(t, client, jwt.MapClaims{
"email": "alice@coder.com",
})
runner.AssertGroups(t, "alice", []string{})
})
t.Run("AddThenRemoveOnReAuth", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
const groupName = "bingbong"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
},
})
ctx := testutil.Context(t, testutil.WaitShort)
group, err := runner.AdminClient.CreateGroup(ctx, runner.AdminUser.OrganizationIDs[0], codersdk.CreateGroupRequest{
Name: groupName,
})
require.NoError(t, err)
require.Len(t, group.Members, 0)
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{groupName},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{groupName})
// Refresh without the group claim
_, resp = runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{})
})
// Updating groups where the claimed group does not exist.
t.Run("NoneMatch", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
},
})
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{"not-exists"},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{})
})
// Updating groups where the claimed group does not exist creates
// the group.
t.Run("AutoCreate", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
const groupName = "make-me"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
cfg.CreateMissingGroups = true
},
})
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
groupClaim: []string{groupName},
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{groupName})
})
// Some IDPs (ADFS) send the "string" type vs "[]string" if only
// 1 group exists.
t.Run("SingleRoleGroup", func(t *testing.T) {
t.Parallel()
const groupClaim = "custom-groups"
const groupName = "bingbong"
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.GroupField = groupClaim
cfg.CreateMissingGroups = true
},
})
// User starts with the owner role
_, resp := runner.Login(t, jwt.MapClaims{
"email": "alice@coder.com",
// This is sent as a **string** intentionally instead
// of an array.
groupClaim: groupName,
})
require.Equal(t, http.StatusOK, resp.StatusCode)
runner.AssertGroups(t, "alice", []string{groupName})
})
})
t.Run("Refresh", func(t *testing.T) {
t.Run("RefreshTokensMultiple", func(t *testing.T) {
t.Parallel()
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.UserRoleField = "roles"
},
})
claims := jwt.MapClaims{
"email": "alice@coder.com",
}
// Login a new client that signs up
client, resp := runner.Login(t, claims)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Refresh multiple times.
for i := 0; i < 3; i++ {
runner.ForceRefresh(t, client, claims)
}
})
t.Run("FailedRefresh", func(t *testing.T) {
t.Parallel()
runner := setupOIDCTest(t, oidcTestConfig{
FakeOpts: []oidctest.FakeIDPOpt{
oidctest.WithRefresh(func(_ string) error {
// Always "expired" refresh token.
return xerrors.New("refresh token is expired")
}),
},
Config: func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
},
})
claims := jwt.MapClaims{
"email": "alice@coder.com",
}
// Login a new client that signs up
client, resp := runner.Login(t, claims)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Expire the token, cause a refresh
runner.ExpireOauthToken(t, client)
// This should fail because the oauth token refresh should fail.
_, err := client.User(context.Background(), codersdk.Me)
require.Error(t, err)
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusUnauthorized, apiError.StatusCode())
require.ErrorContains(t, apiError, "refresh")
})
})
}
// nolint:bodyclose
func TestGroupSync(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
modCfg func(cfg *coderd.OIDCConfig)
// initialOrgGroups is initial groups in the org
initialOrgGroups []string
// initialUserGroups is initial groups for the user
initialUserGroups []string
// expectedUserGroups is expected groups for the user
expectedUserGroups []string
// expectedOrgGroups is expected all groups on the system
expectedOrgGroups []string
claims jwt.MapClaims
}{
{
name: "NoGroups",
modCfg: func(cfg *coderd.OIDCConfig) {
},
initialOrgGroups: []string{},
expectedUserGroups: []string{},
expectedOrgGroups: []string{},
claims: jwt.MapClaims{},
},
{
name: "GroupSyncDisabled",
modCfg: func(cfg *coderd.OIDCConfig) {
// Disable group sync
cfg.GroupField = ""
cfg.GroupFilter = regexp.MustCompile(".*")
},
initialOrgGroups: []string{"a", "b", "c", "d"},
initialUserGroups: []string{"b", "c", "d"},
expectedUserGroups: []string{"b", "c", "d"},
expectedOrgGroups: []string{"a", "b", "c", "d"},
claims: jwt.MapClaims{},
},
{
// From a,c,b -> b,c,d
name: "ChangeUserGroups",
modCfg: func(cfg *coderd.OIDCConfig) {
cfg.GroupMapping = map[string]string{
"D": "d",
}
},
initialOrgGroups: []string{"a", "b", "c", "d"},
initialUserGroups: []string{"a", "b", "c"},
expectedUserGroups: []string{"b", "c", "d"},
expectedOrgGroups: []string{"a", "b", "c", "d"},
claims: jwt.MapClaims{
// D -> d mapped
"groups": []string{"b", "c", "D"},
},
},
{
// From a,c,b -> []
name: "RemoveAllGroups",
modCfg: func(cfg *coderd.OIDCConfig) {
cfg.GroupFilter = regexp.MustCompile(".*")
},
initialOrgGroups: []string{"a", "b", "c", "d"},
initialUserGroups: []string{"a", "b", "c"},
expectedUserGroups: []string{},
expectedOrgGroups: []string{"a", "b", "c", "d"},
claims: jwt.MapClaims{
// No claim == no groups
},
},
{
// From a,c,b -> b,c,d,e,f
name: "CreateMissingGroups",
modCfg: func(cfg *coderd.OIDCConfig) {
cfg.CreateMissingGroups = true
},
initialOrgGroups: []string{"a", "b", "c", "d"},
initialUserGroups: []string{"a", "b", "c"},
expectedUserGroups: []string{"b", "c", "d", "e", "f"},
expectedOrgGroups: []string{"a", "b", "c", "d", "e", "f"},
claims: jwt.MapClaims{
"groups": []string{"b", "c", "d", "e", "f"},
},
},
{
// From a,c,b -> b,c,d,e,f
name: "CreateMissingGroupsFilter",
modCfg: func(cfg *coderd.OIDCConfig) {
cfg.CreateMissingGroups = true
// Only single letter groups
cfg.GroupFilter = regexp.MustCompile("^[a-z]$")
cfg.GroupMapping = map[string]string{
// Does not match the filter, but does after being mapped!
"zebra": "z",
}
},
initialOrgGroups: []string{"a", "b", "c", "d"},
initialUserGroups: []string{"a", "b", "c"},
expectedUserGroups: []string{"b", "c", "d", "e", "f", "z"},
expectedOrgGroups: []string{"a", "b", "c", "d", "e", "f", "z"},
claims: jwt.MapClaims{
"groups": []string{
"b", "c", "d", "e", "f",
// These groups are ignored
"excess", "ignore", "dumb", "foobar", "zebra",
},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
runner := setupOIDCTest(t, oidcTestConfig{
Config: func(cfg *coderd.OIDCConfig) {
cfg.GroupField = "groups"
tc.modCfg(cfg)
},
})
// Setup
ctx := testutil.Context(t, testutil.WaitLong)
org := runner.AdminUser.OrganizationIDs[0]
initialGroups := make(map[string]codersdk.Group)
for _, group := range tc.initialOrgGroups {
newGroup, err := runner.AdminClient.CreateGroup(ctx, org, codersdk.CreateGroupRequest{
Name: group,
})
require.NoError(t, err)
require.Len(t, newGroup.Members, 0)
initialGroups[group] = newGroup
}
// Create the user and add them to their initial groups
_, user := coderdtest.CreateAnotherUser(t, runner.AdminClient, org)
for _, group := range tc.initialUserGroups {
_, err := runner.AdminClient.PatchGroup(ctx, initialGroups[group].ID, codersdk.PatchGroupRequest{
AddUsers: []string{user.ID.String()},
})
require.NoError(t, err)
}
// nolint:gocritic
_, err := runner.API.Database.UpdateUserLoginType(dbauthz.AsSystemRestricted(ctx), database.UpdateUserLoginTypeParams{
NewLoginType: database.LoginTypeOIDC,
UserID: user.ID,
})
require.NoError(t, err, "user must be oidc type")
// Log in the new user
tc.claims["email"] = user.Email
_, resp := runner.Login(t, tc.claims)
require.Equal(t, http.StatusOK, resp.StatusCode)
// Check group sources
orgGroups, err := runner.AdminClient.GroupsByOrganization(ctx, org)
require.NoError(t, err)
for _, group := range orgGroups {
if slice.Contains(tc.initialOrgGroups, group.Name) || group.IsEveryone() {
require.Equal(t, group.Source, codersdk.GroupSourceUser)
} else {
require.Equal(t, group.Source, codersdk.GroupSourceOIDC)
}
}
orgGroupsMap := make(map[string]struct{})
for _, group := range orgGroups {
orgGroupsMap[group.Name] = struct{}{}
}
for _, expected := range tc.expectedOrgGroups {
if _, ok := orgGroupsMap[expected]; !ok {
t.Errorf("expected group %s not found", expected)
}
delete(orgGroupsMap, expected)
}
delete(orgGroupsMap, database.EveryoneGroup)
require.Empty(t, orgGroupsMap, "unexpected groups found")
expectedUserGroups := make(map[string]struct{})
for _, group := range tc.expectedUserGroups {
expectedUserGroups[group] = struct{}{}
}
for _, group := range orgGroups {
userInGroup := slice.ContainsCompare(group.Members, codersdk.User{Email: user.Email}, func(a, b codersdk.User) bool {
return a.Email == b.Email
})
if group.IsEveryone() {
require.True(t, userInGroup, "user cannot be removed from 'Everyone' group")
} else if _, ok := expectedUserGroups[group.Name]; ok {
require.Truef(t, userInGroup, "user should be in group %s", group.Name)
} else {
require.Falsef(t, userInGroup, "user should not be in group %s", group.Name)
}
}
})
}
}
// oidcTestRunner is just a helper to setup and run oidc tests.
// An actual Coderd instance is used to run the tests.
type oidcTestRunner struct {
AdminClient *codersdk.Client
AdminUser codersdk.User
API *coderden.API
// Login will call the OIDC flow with an unauthenticated client.
// The IDP will return the idToken claims.
Login func(t *testing.T, idToken jwt.MapClaims) (*codersdk.Client, *http.Response)
// ForceRefresh will use an authenticated codersdk.Client, and force their
// OIDC token to be expired and require a refresh. The refresh will use the claims provided.
// It just calls the /users/me endpoint to trigger the refresh.
ForceRefresh func(t *testing.T, client *codersdk.Client, idToken jwt.MapClaims)
ExpireOauthToken func(t *testing.T, client *codersdk.Client)
}
type oidcTestConfig struct {
Userinfo jwt.MapClaims
// Config allows modifying the Coderd OIDC configuration.
Config func(cfg *coderd.OIDCConfig)
FakeOpts []oidctest.FakeIDPOpt
}
func (r *oidcTestRunner) AssertRoles(t *testing.T, userIdent string, roles []string) {
t.Helper()
ctx := testutil.Context(t, testutil.WaitMedium)
user, err := r.AdminClient.User(ctx, userIdent)
require.NoError(t, err)
roleNames := []string{}
for _, role := range user.Roles {
roleNames = append(roleNames, role.Name)
}
require.ElementsMatch(t, roles, roleNames, "expected roles")
}
func (r *oidcTestRunner) AssertGroups(t *testing.T, userIdent string, groups []string) {
t.Helper()
if !slice.Contains(groups, database.EveryoneGroup) {
var cpy []string
cpy = append(cpy, groups...)
// always include everyone group
cpy = append(cpy, database.EveryoneGroup)
groups = cpy
}
ctx := testutil.Context(t, testutil.WaitMedium)
user, err := r.AdminClient.User(ctx, userIdent)
require.NoError(t, err)
allGroups, err := r.AdminClient.GroupsByOrganization(ctx, user.OrganizationIDs[0])
require.NoError(t, err)
userInGroups := []string{}
for _, g := range allGroups {
for _, mem := range g.Members {
if mem.ID == user.ID {
userInGroups = append(userInGroups, g.Name)
}
}
}
require.ElementsMatch(t, groups, userInGroups, "expected groups")
}
func setupOIDCTest(t *testing.T, settings oidcTestConfig) *oidcTestRunner {
t.Helper()
fake := oidctest.NewFakeIDP(t,
append([]oidctest.FakeIDPOpt{
oidctest.WithStaticUserInfo(settings.Userinfo),
oidctest.WithLogging(t, nil),
// Run fake IDP on a real webserver
oidctest.WithServing(),
}, settings.FakeOpts...)...,
)
ctx := testutil.Context(t, testutil.WaitMedium)
cfg := fake.OIDCConfig(t, nil, settings.Config)
owner, _, api, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
OIDCConfig: cfg,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureUserRoleManagement: 1,
codersdk.FeatureTemplateRBAC: 1,
},
},
})
admin, err := owner.User(ctx, "me")
require.NoError(t, err)
helper := oidctest.NewLoginHelper(owner, fake)
return &oidcTestRunner{
AdminClient: owner,
AdminUser: admin,
API: api,
Login: helper.Login,
ForceRefresh: func(t *testing.T, client *codersdk.Client, idToken jwt.MapClaims) {
helper.ForceRefresh(t, api.Database, client, idToken)
},
ExpireOauthToken: func(t *testing.T, client *codersdk.Client) {
helper.ExpireOauthToken(t, api.Database, client)
},
}
}