@@ -20,7 +20,6 @@ import (
20
20
"github.com/google/go-github/v43/github"
21
21
"github.com/google/uuid"
22
22
"github.com/moby/moby/pkg/namesgenerator"
23
- "golang.org/x/exp/slices"
24
23
"golang.org/x/oauth2"
25
24
"golang.org/x/xerrors"
26
25
@@ -659,6 +658,9 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
659
658
AvatarURL :ghUser .GetAvatarURL (),
660
659
Name :normName ,
661
660
DebugContext :OauthDebugContext {},
661
+ GroupSync : idpsync.GroupParams {
662
+ SyncEnabled :false ,
663
+ },
662
664
OrganizationSync : idpsync.OrganizationParams {
663
665
SyncEnabled :false ,
664
666
IncludeDefault :true ,
@@ -1004,11 +1006,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1004
1006
}
1005
1007
1006
1008
ctx = slog .With (ctx ,slog .F ("email" ,email ),slog .F ("username" ,username ),slog .F ("name" ,name ))
1007
- usingGroups ,groups ,groupErr := api .oidcGroups (ctx ,mergedClaims )
1008
- if groupErr != nil {
1009
- groupErr .Write (rw ,r )
1010
- return
1011
- }
1012
1009
1013
1010
roles ,roleErr := api .oidcRoles (ctx ,mergedClaims )
1014
1011
if roleErr != nil {
@@ -1032,30 +1029,33 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1032
1029
return
1033
1030
}
1034
1031
1032
+ groupSync ,groupSyncErr := api .IDPSync .ParseGroupClaims (ctx ,mergedClaims )
1033
+ if groupSyncErr != nil {
1034
+ groupSyncErr .Write (rw ,r )
1035
+ return
1036
+ }
1037
+
1035
1038
// If a new user is authenticating for the first time
1036
1039
// the audit action is 'register', not 'login'
1037
1040
if user .ID == uuid .Nil {
1038
1041
aReq .Action = database .AuditActionRegister
1039
1042
}
1040
1043
1041
1044
params := (& oauthLoginParams {
1042
- User :user ,
1043
- Link :link ,
1044
- State :state ,
1045
- LinkedID :oidcLinkedID (idToken ),
1046
- LoginType :database .LoginTypeOIDC ,
1047
- AllowSignups :api .OIDCConfig .AllowSignups ,
1048
- Email :email ,
1049
- Username :username ,
1050
- Name :name ,
1051
- AvatarURL :picture ,
1052
- UsingRoles :api .OIDCConfig .RoleSyncEnabled (),
1053
- Roles :roles ,
1054
- UsingGroups :usingGroups ,
1055
- Groups :groups ,
1056
- OrganizationSync :orgSync ,
1057
- CreateMissingGroups :api .OIDCConfig .CreateMissingGroups ,
1058
- GroupFilter :api .OIDCConfig .GroupFilter ,
1045
+ User :user ,
1046
+ Link :link ,
1047
+ State :state ,
1048
+ LinkedID :oidcLinkedID (idToken ),
1049
+ LoginType :database .LoginTypeOIDC ,
1050
+ AllowSignups :api .OIDCConfig .AllowSignups ,
1051
+ Email :email ,
1052
+ Username :username ,
1053
+ Name :name ,
1054
+ AvatarURL :picture ,
1055
+ UsingRoles :api .OIDCConfig .RoleSyncEnabled (),
1056
+ Roles :roles ,
1057
+ OrganizationSync :orgSync ,
1058
+ GroupSync :groupSync ,
1059
1059
DebugContext :OauthDebugContext {
1060
1060
IDTokenClaims :idtokenClaims ,
1061
1061
UserInfoClaims :userInfoClaims ,
@@ -1091,79 +1091,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1091
1091
http .Redirect (rw ,r ,redirect ,http .StatusTemporaryRedirect )
1092
1092
}
1093
1093
1094
- // oidcGroups returns the groups for the user from the OIDC claims.
1095
- func (api * API )oidcGroups (ctx context.Context ,mergedClaims map [string ]interface {}) (bool , []string ,* idpsync.HTTPError ) {
1096
- logger := api .Logger .Named (userAuthLoggerName )
1097
- usingGroups := false
1098
- var groups []string
1099
-
1100
- // If the GroupField is the empty string, then groups from OIDC are not used.
1101
- // This is so we can support manual group assignment.
1102
- if api .OIDCConfig .GroupField != "" {
1103
- // If the allow list is empty, then the user is allowed to log in.
1104
- // Otherwise, they must belong to at least 1 group in the allow list.
1105
- inAllowList := len (api .OIDCConfig .GroupAllowList )== 0
1106
-
1107
- usingGroups = true
1108
- groupsRaw ,ok := mergedClaims [api .OIDCConfig .GroupField ]
1109
- if ok {
1110
- parsedGroups ,err := idpsync .ParseStringSliceClaim (groupsRaw )
1111
- if err != nil {
1112
- api .Logger .Debug (ctx ,"groups field was an unknown type in oidc claims" ,
1113
- slog .F ("type" ,fmt .Sprintf ("%T" ,groupsRaw )),
1114
- slog .Error (err ),
1115
- )
1116
- return false ,nil ,& idpsync.HTTPError {
1117
- Code :http .StatusBadRequest ,
1118
- Msg :"Failed to sync groups from OIDC claims" ,
1119
- Detail :err .Error (),
1120
- RenderStaticPage :false ,
1121
- }
1122
- }
1123
-
1124
- api .Logger .Debug (ctx ,"groups returned in oidc claims" ,
1125
- slog .F ("len" ,len (parsedGroups )),
1126
- slog .F ("groups" ,parsedGroups ),
1127
- )
1128
-
1129
- for _ ,group := range parsedGroups {
1130
- if mappedGroup ,ok := api .OIDCConfig .GroupMapping [group ];ok {
1131
- group = mappedGroup
1132
- }
1133
- if _ ,ok := api .OIDCConfig .GroupAllowList [group ];ok {
1134
- inAllowList = true
1135
- }
1136
- groups = append (groups ,group )
1137
- }
1138
- }
1139
-
1140
- if ! inAllowList {
1141
- logger .Debug (ctx ,"oidc group claim not in allow list, rejecting login" ,
1142
- slog .F ("allow_list_count" ,len (api .OIDCConfig .GroupAllowList )),
1143
- slog .F ("user_group_count" ,len (groups )),
1144
- )
1145
- detail := "Ask an administrator to add one of your groups to the allow list"
1146
- if len (groups )== 0 {
1147
- detail = "You are currently not a member of any groups! Ask an administrator to add you to an authorized group to login."
1148
- }
1149
- return usingGroups ,groups ,& idpsync.HTTPError {
1150
- Code :http .StatusForbidden ,
1151
- Msg :"Not a member of an allowed group" ,
1152
- Detail :detail ,
1153
- RenderStaticPage :true ,
1154
- }
1155
- }
1156
- }
1157
-
1158
- // This conditional is purely to warn the user they might have misconfigured their OIDC
1159
- // configuration.
1160
- if _ ,groupClaimExists := mergedClaims ["groups" ];! usingGroups && groupClaimExists {
1161
- logger .Debug (ctx ,"claim 'groups' was returned, but 'oidc-group-field' is not set, check your coder oidc settings" )
1162
- }
1163
-
1164
- return usingGroups ,groups ,nil
1165
- }
1166
-
1167
1094
// oidcRoles returns the roles for the user from the OIDC claims.
1168
1095
// If the function returns false, then the caller should return early.
1169
1096
// All writes to the response writer are handled by this function.
@@ -1278,14 +1205,7 @@ type oauthLoginParams struct {
1278
1205
AvatarURL string
1279
1206
// OrganizationSync has the organizations that the user will be assigned to.
1280
1207
OrganizationSync idpsync.OrganizationParams
1281
- // Is UsingGroups is true, then the user will be assigned
1282
- // to the Groups provided.
1283
- UsingGroups bool
1284
- CreateMissingGroups bool
1285
- // These are the group names from the IDP. Internally, they will map to
1286
- // some organization groups.
1287
- Groups []string
1288
- GroupFilter * regexp.Regexp
1208
+ GroupSync idpsync.GroupParams
1289
1209
// Is UsingRoles is true, then the user will be assigned
1290
1210
// the roles provided.
1291
1211
UsingRoles bool
@@ -1491,53 +1411,9 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
1491
1411
return xerrors .Errorf ("sync organizations: %w" ,err )
1492
1412
}
1493
1413
1494
- // Ensure groups are correct.
1495
- // This places all groups into the default organization.
1496
- // To go multi-org, we need to add a mapping feature here to know which
1497
- // groups go to which orgs.
1498
- if params .UsingGroups {
1499
- filtered := params .Groups
1500
- if params .GroupFilter != nil {
1501
- filtered = make ([]string ,0 ,len (params .Groups ))
1502
- for _ ,group := range params .Groups {
1503
- if params .GroupFilter .MatchString (group ) {
1504
- filtered = append (filtered ,group )
1505
- }
1506
- }
1507
- }
1508
-
1509
- //nolint:gocritic // No user present in the context.
1510
- defaultOrganization ,err := tx .GetDefaultOrganization (dbauthz .AsSystemRestricted (ctx ))
1511
- if err != nil {
1512
- // If there is no default org, then we can't assign groups.
1513
- // By default, we assume all groups belong to the default org.
1514
- return xerrors .Errorf ("get default organization: %w" ,err )
1515
- }
1516
-
1517
- //nolint:gocritic // No user present in the context.
1518
- memberships ,err := tx .OrganizationMembers (dbauthz .AsSystemRestricted (ctx ), database.OrganizationMembersParams {
1519
- UserID :user .ID ,
1520
- OrganizationID :uuid .Nil ,
1521
- })
1522
- if err != nil {
1523
- return xerrors .Errorf ("get organization memberships: %w" ,err )
1524
- }
1525
-
1526
- // If the user is not in the default organization, then we can't assign groups.
1527
- // A user cannot be in groups to an org they are not a member of.
1528
- if ! slices .ContainsFunc (memberships ,func (member database.OrganizationMembersRow )bool {
1529
- return member .OrganizationMember .OrganizationID == defaultOrganization .ID
1530
- }) {
1531
- return xerrors .Errorf ("user %s is not a member of the default organization, cannot assign to groups in the org" ,user .ID )
1532
- }
1533
-
1534
- //nolint:gocritic
1535
- err = api .Options .SetUserGroups (dbauthz .AsSystemRestricted (ctx ),logger ,tx ,user .ID ,map [uuid.UUID ][]string {
1536
- defaultOrganization .ID :filtered ,
1537
- },params .CreateMissingGroups )
1538
- if err != nil {
1539
- return xerrors .Errorf ("set user groups: %w" ,err )
1540
- }
1414
+ err = api .IDPSync .SyncGroups (ctx ,tx ,user ,params .GroupSync )
1415
+ if err != nil {
1416
+ return xerrors .Errorf ("sync groups: %w" ,err )
1541
1417
}
1542
1418
1543
1419
// Ensure roles are correct.