Skip to content

Commit 7d4977e

Browse files
committed
handle allow list
1 parent 30d0f83 commit 7d4977e

File tree

7 files changed

+110
-12
lines changed

7 files changed

+110
-12
lines changed

coderd/coderd.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,7 @@ func New(options *Options) *API {
312312
)
313313

314314
if options.IDPSync == nil {
315-
options.IDPSync = idpsync.NewAGPLSync(options.Logger, idpsync.DeploymentSyncSettings{
316-
OrganizationField: options.DeploymentValues.OIDC.OrganizationField.Value(),
317-
OrganizationMapping: options.DeploymentValues.OIDC.OrganizationMapping.Value,
318-
OrganizationAssignDefault: options.DeploymentValues.OIDC.OrganizationAssignDefault.Value(),
319-
})
315+
options.IDPSync = idpsync.NewAGPLSync(options.Logger, options.RuntimeConfig, idpsync.FromDeploymentValues(options.DeploymentValues))
320316
}
321317

322318
experiments := ReadExperiments(

coderd/idpsync/group.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,11 @@ func (s GroupSyncSettings) HandleMissingGroups(ctx context.Context, tx database.
266266

267267
return addIDs, nil
268268
}
269+
270+
func ConvertAllowList(allowList []string) map[string]struct{} {
271+
allowMap := make(map[string]struct{}, len(allowList))
272+
for _, group := range allowList {
273+
allowMap[group] = struct{}{}
274+
}
275+
return allowMap
276+
}

coderd/idpsync/idpsync.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,29 @@ type DeploymentSyncSettings struct {
6363
// placed into the default organization. This is mostly a hack to support
6464
// legacy deployments.
6565
OrganizationAssignDefault bool
66+
67+
// GroupField at the deployment level is used for deployment level group claim
68+
// settings.
69+
GroupField string
70+
// GroupAllowList (if set) will restrict authentication to only users who
71+
// have at least one group in this list.
72+
// A map representation is used for easier lookup.
73+
GroupAllowList map[string]struct{}
74+
}
75+
76+
func FromDeploymentValues(dv *codersdk.DeploymentValues) DeploymentSyncSettings {
77+
if dv == nil {
78+
panic("Developer error: DeploymentValues should not be nil")
79+
}
80+
return DeploymentSyncSettings{
81+
OrganizationField: dv.OIDC.OrganizationField.Value(),
82+
OrganizationMapping: dv.OIDC.OrganizationMapping.Value,
83+
OrganizationAssignDefault: dv.OIDC.OrganizationAssignDefault.Value(),
84+
85+
GroupField: dv.OIDC.GroupField.Value(),
86+
GroupAllowList: ConvertAllowList(dv.OIDC.GroupAllowList.Value()),
87+
}
88+
6689
}
6790

6891
type SyncSettings struct {

coderd/userauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ func (api *API) oidcGroups(ctx context.Context, mergedClaims map[string]interfac
11421142
slog.F("allow_list_count", len(api.OIDCConfig.GroupAllowList)),
11431143
slog.F("user_group_count", len(groups)),
11441144
)
1145-
detail := "Ask an administrator to add one of your groups to the whitelist"
1145+
detail := "Ask an administrator to add one of your groups to the allow list"
11461146
if len(groups) == 0 {
11471147
detail = "You are currently not a member of any groups! Ask an administrator to add you to an authorized group to login."
11481148
}

enterprise/coderd/coderd.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
113113
options.Database = cryptDB
114114

115115
if options.IDPSync == nil {
116-
options.IDPSync = enidpsync.NewSync(options.Logger, options.Entitlements, idpsync.SyncSettings{
117-
OrganizationField: options.DeploymentValues.OIDC.OrganizationField.Value(),
118-
OrganizationMapping: options.DeploymentValues.OIDC.OrganizationMapping.Value,
119-
OrganizationAssignDefault: options.DeploymentValues.OIDC.OrganizationAssignDefault.Value(),
120-
})
116+
options.IDPSync = enidpsync.NewSync(options.Logger, options.RuntimeConfig, options.Entitlements, idpsync.FromDeploymentValues(options.DeploymentValues))
121117
}
122118

123119
api := &API{

enterprise/coderd/enidpsync/groups.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package enidpsync
22

33
import (
44
"context"
5+
"net/http"
56

67
"github.com/golang-jwt/jwt/v4"
78

@@ -11,7 +12,6 @@ import (
1112

1213
func (e EnterpriseIDPSync) GroupSyncEnabled() bool {
1314
return e.entitlements.Enabled(codersdk.FeatureTemplateRBAC)
14-
1515
}
1616

1717
// ParseGroupClaims parses the user claims and handles deployment wide group behavior.
@@ -23,6 +23,46 @@ func (e EnterpriseIDPSync) ParseGroupClaims(ctx context.Context, mergedClaims jw
2323
return e.AGPLIDPSync.ParseGroupClaims(ctx, mergedClaims)
2424
}
2525

26+
if e.GroupField != "" && len(e.GroupAllowList) > 0 {
27+
groupsRaw, ok := mergedClaims[e.GroupField]
28+
if !ok {
29+
return idpsync.GroupParams{}, &idpsync.HTTPError{
30+
Code: http.StatusForbidden,
31+
Msg: "Not a member of an allowed group",
32+
Detail: "You have no groups in your claims!",
33+
RenderStaticPage: true,
34+
}
35+
}
36+
parsedGroups, err := idpsync.ParseStringSliceClaim(groupsRaw)
37+
if err != nil {
38+
return idpsync.GroupParams{}, &idpsync.HTTPError{
39+
Code: http.StatusBadRequest,
40+
Msg: "Failed read groups from claims for allow list check. Ask an administrator for help.",
41+
Detail: err.Error(),
42+
RenderStaticPage: true,
43+
}
44+
}
45+
46+
inAllowList := false
47+
AllowListCheckLoop:
48+
for _, group := range parsedGroups {
49+
if _, ok := e.GroupAllowList[group]; ok {
50+
inAllowList = true
51+
break AllowListCheckLoop
52+
}
53+
}
54+
55+
if !inAllowList {
56+
return idpsync.GroupParams{}, &idpsync.HTTPError{
57+
Code: http.StatusForbidden,
58+
Msg: "Not a member of an allowed group",
59+
Detail: "Ask an administrator to add one of your groups to the allow list.",
60+
RenderStaticPage: true,
61+
}
62+
}
63+
64+
}
65+
2666
return idpsync.GroupParams{
2767
SyncEnabled: e.OrganizationSyncEnabled(),
2868
MergedClaims: mergedClaims,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package enidpsync_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golang-jwt/jwt/v4"
7+
"github.com/stretchr/testify/require"
8+
9+
"cdr.dev/slog/sloggers/slogtest"
10+
"github.com/coder/coder/v2/coderd/entitlements"
11+
"github.com/coder/coder/v2/coderd/idpsync"
12+
"github.com/coder/coder/v2/coderd/runtimeconfig"
13+
"github.com/coder/coder/v2/enterprise/coderd/enidpsync"
14+
"github.com/coder/coder/v2/testutil"
15+
)
16+
17+
func TestEnterpriseParseGroupClaims(t *testing.T) {
18+
t.Parallel()
19+
20+
t.Run("NoEntitlements", func(t *testing.T) {
21+
t.Parallel()
22+
23+
s := enidpsync.NewSync(slogtest.Make(t, &slogtest.Options{}),
24+
runtimeconfig.NewNoopManager(),
25+
entitlements.New(),
26+
idpsync.DeploymentSyncSettings{})
27+
28+
ctx := testutil.Context(t, testutil.WaitMedium)
29+
30+
params, err := s.ParseGroupClaims(ctx, jwt.MapClaims{})
31+
require.Nil(t, err)
32+
33+
require.False(t, params.SyncEnabled)
34+
})
35+
}

0 commit comments

Comments
 (0)