Skip to content

Commit 5b1e809

Browse files
authored
fix: support oidc group allowlist in oss (#19430)
## Summary In this pull request we're adding support for OIDC allowed groups in the OSS version as part of work for #17027. ### Changes - Restored support for parsing group allow list in OSS code ### Testing - Added tests for OSS code - Tested allowed/prohibited group OIDC flows in premium and OSS
1 parent a19dfa9 commit 5b1e809

File tree

3 files changed

+80
-50
lines changed

3 files changed

+80
-50
lines changed

coderd/idpsync/group.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"net/http"
78

89
"github.com/golang-jwt/jwt/v4"
910
"github.com/google/uuid"
@@ -71,9 +72,49 @@ func (s AGPLIDPSync) GroupSyncSettings(ctx context.Context, orgID uuid.UUID, db
7172
return settings, nil
7273
}
7374

74-
func (s AGPLIDPSync) ParseGroupClaims(_ context.Context, _ jwt.MapClaims) (GroupParams, *HTTPError) {
75+
func (s AGPLIDPSync) ParseGroupClaims(_ context.Context, mergedClaims jwt.MapClaims) (GroupParams, *HTTPError) {
76+
if s.GroupField != "" && len(s.GroupAllowList) > 0 {
77+
groupsRaw, ok := mergedClaims[s.GroupField]
78+
if !ok {
79+
return GroupParams{}, &HTTPError{
80+
Code: http.StatusForbidden,
81+
Msg: "Not a member of an allowed group",
82+
Detail: "You have no groups in your claims!",
83+
RenderStaticPage: true,
84+
}
85+
}
86+
parsedGroups, err := ParseStringSliceClaim(groupsRaw)
87+
if err != nil {
88+
return GroupParams{}, &HTTPError{
89+
Code: http.StatusBadRequest,
90+
Msg: "Failed read groups from claims for allow list check. Ask an administrator for help.",
91+
Detail: err.Error(),
92+
RenderStaticPage: true,
93+
}
94+
}
95+
96+
inAllowList := false
97+
AllowListCheckLoop:
98+
for _, group := range parsedGroups {
99+
if _, ok := s.GroupAllowList[group]; ok {
100+
inAllowList = true
101+
break AllowListCheckLoop
102+
}
103+
}
104+
105+
if !inAllowList {
106+
return GroupParams{}, &HTTPError{
107+
Code: http.StatusForbidden,
108+
Msg: "Not a member of an allowed group",
109+
Detail: "Ask an administrator to add one of your groups to the allow list.",
110+
RenderStaticPage: true,
111+
}
112+
}
113+
}
114+
75115
return GroupParams{
76116
SyncEntitled: s.GroupSyncEntitled(),
117+
MergedClaims: mergedClaims,
77118
}, nil
78119
}
79120

coderd/idpsync/group_test.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ func TestParseGroupClaims(t *testing.T) {
4444
require.False(t, params.SyncEntitled)
4545
})
4646

47-
// AllowList has no effect in AGPL
48-
t.Run("AllowList", func(t *testing.T) {
47+
t.Run("NotInAllowList", func(t *testing.T) {
4948
t.Parallel()
5049

5150
s := idpsync.NewAGPLSync(slogtest.Make(t, &slogtest.Options{}),
@@ -59,9 +58,39 @@ func TestParseGroupClaims(t *testing.T) {
5958

6059
ctx := testutil.Context(t, testutil.WaitMedium)
6160

62-
params, err := s.ParseGroupClaims(ctx, jwt.MapClaims{})
61+
// Invalid group
62+
_, err := s.ParseGroupClaims(ctx, jwt.MapClaims{
63+
"groups": []string{"bar"},
64+
})
65+
require.NotNil(t, err)
66+
require.Equal(t, 403, err.Code)
67+
68+
// No groups
69+
_, err = s.ParseGroupClaims(ctx, jwt.MapClaims{})
70+
require.NotNil(t, err)
71+
require.Equal(t, 403, err.Code)
72+
})
73+
74+
t.Run("InAllowList", func(t *testing.T) {
75+
t.Parallel()
76+
77+
s := idpsync.NewAGPLSync(slogtest.Make(t, &slogtest.Options{}),
78+
runtimeconfig.NewManager(),
79+
idpsync.DeploymentSyncSettings{
80+
GroupField: "groups",
81+
GroupAllowList: map[string]struct{}{
82+
"foo": {},
83+
},
84+
})
85+
86+
ctx := testutil.Context(t, testutil.WaitMedium)
87+
88+
claims := jwt.MapClaims{
89+
"groups": []string{"foo", "bar"},
90+
}
91+
params, err := s.ParseGroupClaims(ctx, claims)
6392
require.Nil(t, err)
64-
require.False(t, params.SyncEntitled)
93+
require.Equal(t, claims, params.MergedClaims)
6594
})
6695
}
6796

enterprise/coderd/enidpsync/groups.go

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package enidpsync
22

33
import (
44
"context"
5-
"net/http"
65

76
"github.com/golang-jwt/jwt/v4"
87

@@ -20,51 +19,12 @@ func (e EnterpriseIDPSync) GroupSyncEntitled() bool {
2019
// GroupAllowList is implemented here to prevent login by unauthorized users.
2120
// TODO: GroupAllowList overlaps with the default organization group sync settings.
2221
func (e EnterpriseIDPSync) ParseGroupClaims(ctx context.Context, mergedClaims jwt.MapClaims) (idpsync.GroupParams, *idpsync.HTTPError) {
23-
if !e.GroupSyncEntitled() {
24-
return e.AGPLIDPSync.ParseGroupClaims(ctx, mergedClaims)
22+
resp, err := e.AGPLIDPSync.ParseGroupClaims(ctx, mergedClaims)
23+
if err != nil {
24+
return idpsync.GroupParams{}, err
2525
}
26-
27-
if e.GroupField != "" && len(e.GroupAllowList) > 0 {
28-
groupsRaw, ok := mergedClaims[e.GroupField]
29-
if !ok {
30-
return idpsync.GroupParams{}, &idpsync.HTTPError{
31-
Code: http.StatusForbidden,
32-
Msg: "Not a member of an allowed group",
33-
Detail: "You have no groups in your claims!",
34-
RenderStaticPage: true,
35-
}
36-
}
37-
parsedGroups, err := idpsync.ParseStringSliceClaim(groupsRaw)
38-
if err != nil {
39-
return idpsync.GroupParams{}, &idpsync.HTTPError{
40-
Code: http.StatusBadRequest,
41-
Msg: "Failed read groups from claims for allow list check. Ask an administrator for help.",
42-
Detail: err.Error(),
43-
RenderStaticPage: true,
44-
}
45-
}
46-
47-
inAllowList := false
48-
AllowListCheckLoop:
49-
for _, group := range parsedGroups {
50-
if _, ok := e.GroupAllowList[group]; ok {
51-
inAllowList = true
52-
break AllowListCheckLoop
53-
}
54-
}
55-
56-
if !inAllowList {
57-
return idpsync.GroupParams{}, &idpsync.HTTPError{
58-
Code: http.StatusForbidden,
59-
Msg: "Not a member of an allowed group",
60-
Detail: "Ask an administrator to add one of your groups to the allow list.",
61-
RenderStaticPage: true,
62-
}
63-
}
64-
}
65-
6626
return idpsync.GroupParams{
67-
SyncEntitled: true,
68-
MergedClaims: mergedClaims,
27+
SyncEntitled: e.GroupSyncEntitled(),
28+
MergedClaims: resp.MergedClaims,
6929
}, nil
7030
}

0 commit comments

Comments
 (0)