@@ -740,17 +740,6 @@ type OIDCConfig struct {
740
740
// support the userinfo endpoint, or if the userinfo endpoint causes
741
741
// undesirable behavior.
742
742
IgnoreUserInfo bool
743
- // UserRoleField selects the claim field to be used as the created user's
744
- // roles. If the field is the empty string, then no role updates
745
- // will ever come from the OIDC provider.
746
- UserRoleField string
747
- // UserRoleMapping controls how groups returned by the OIDC provider get mapped
748
- // to roles within Coder.
749
- // map[oidcRoleName][]coderRoleName
750
- UserRoleMapping map [string ][]string
751
- // UserRolesDefault is the default set of roles to assign to a user if role sync
752
- // is enabled.
753
- UserRolesDefault []string
754
743
// SignInText is the text to display on the OIDC login button
755
744
SignInText string
756
745
// IconURL points to the URL of an icon to display on the OIDC login button
@@ -759,10 +748,6 @@ type OIDCConfig struct {
759
748
SignupsDisabledText string
760
749
}
761
750
762
- func (cfg OIDCConfig ) RoleSyncEnabled () bool {
763
- return cfg .UserRoleField != ""
764
- }
765
-
766
751
// @Summary OpenID Connect Callback
767
752
// @ID openid-connect-callback
768
753
// @Security CoderSessionToken
@@ -983,12 +968,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
983
968
984
969
ctx = slog .With (ctx , slog .F ("email" , email ), slog .F ("username" , username ), slog .F ("name" , name ))
985
970
986
- roles , roleErr := api .oidcRoles (ctx , mergedClaims )
987
- if roleErr != nil {
988
- roleErr .Write (rw , r )
989
- return
990
- }
991
-
992
971
user , link , err := findLinkedUser (ctx , api .Database , oidcLinkedID (idToken ), email )
993
972
if err != nil {
994
973
logger .Error (ctx , "oauth2: unable to find linked user" , slog .F ("email" , email ), slog .Error (err ))
@@ -1011,6 +990,12 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1011
990
return
1012
991
}
1013
992
993
+ roleSync , roleSyncErr := api .IDPSync .ParseRoleClaims (ctx , mergedClaims )
994
+ if roleSyncErr != nil {
995
+ roleSyncErr .Write (rw , r )
996
+ return
997
+ }
998
+
1014
999
// If a new user is authenticating for the first time
1015
1000
// the audit action is 'register', not 'login'
1016
1001
if user .ID == uuid .Nil {
@@ -1028,10 +1013,9 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1028
1013
Username : username ,
1029
1014
Name : name ,
1030
1015
AvatarURL : picture ,
1031
- UsingRoles : api .OIDCConfig .RoleSyncEnabled (),
1032
- Roles : roles ,
1033
1016
OrganizationSync : orgSync ,
1034
1017
GroupSync : groupSync ,
1018
+ RoleSync : roleSync ,
1035
1019
DebugContext : OauthDebugContext {
1036
1020
IDTokenClaims : idtokenClaims ,
1037
1021
UserInfoClaims : userInfoClaims ,
@@ -1067,61 +1051,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1067
1051
http .Redirect (rw , r , redirect , http .StatusTemporaryRedirect )
1068
1052
}
1069
1053
1070
- // oidcRoles returns the roles for the user from the OIDC claims.
1071
- // If the function returns false, then the caller should return early.
1072
- // All writes to the response writer are handled by this function.
1073
- // It would be preferred to just return an error, however this function
1074
- // decorates returned errors with the appropriate HTTP status codes and details
1075
- // that are hard to carry in a standard `error` without more work.
1076
- func (api * API ) oidcRoles (ctx context.Context , mergedClaims map [string ]interface {}) ([]string , * idpsync.HTTPError ) {
1077
- roles := api .OIDCConfig .UserRolesDefault
1078
- if ! api .OIDCConfig .RoleSyncEnabled () {
1079
- return roles , nil
1080
- }
1081
-
1082
- rolesRow , ok := mergedClaims [api .OIDCConfig .UserRoleField ]
1083
- if ! ok {
1084
- // If no claim is provided than we can assume the user is just
1085
- // a member. This is because there is no way to tell the difference
1086
- // between []string{} and nil for OIDC claims. IDPs omit claims
1087
- // if they are empty ([]string{}).
1088
- // Use []interface{}{} so the next typecast works.
1089
- rolesRow = []interface {}{}
1090
- }
1091
-
1092
- parsedRoles , err := idpsync .ParseStringSliceClaim (rolesRow )
1093
- if err != nil {
1094
- api .Logger .Error (ctx , "oidc claims user roles field was an unknown type" ,
1095
- slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
1096
- slog .Error (err ),
1097
- )
1098
- return nil , & idpsync.HTTPError {
1099
- Code : http .StatusInternalServerError ,
1100
- Msg : "Login disabled until OIDC config is fixed" ,
1101
- Detail : fmt .Sprintf ("Roles claim must be an array of strings, type found: %T. Disabling role sync will allow login to proceed." , rolesRow ),
1102
- RenderStaticPage : false ,
1103
- }
1104
- }
1105
-
1106
- api .Logger .Debug (ctx , "roles returned in oidc claims" ,
1107
- slog .F ("len" , len (parsedRoles )),
1108
- slog .F ("roles" , parsedRoles ),
1109
- )
1110
- for _ , role := range parsedRoles {
1111
- if mappedRoles , ok := api .OIDCConfig .UserRoleMapping [role ]; ok {
1112
- if len (mappedRoles ) == 0 {
1113
- continue
1114
- }
1115
- // Mapped roles are added to the list of roles
1116
- roles = append (roles , mappedRoles ... )
1117
- continue
1118
- }
1119
-
1120
- roles = append (roles , role )
1121
- }
1122
- return roles , nil
1123
- }
1124
-
1125
1054
// claimFields returns the sorted list of fields in the claims map.
1126
1055
func claimFields (claims map [string ]interface {}) []string {
1127
1056
fields := []string {}
@@ -1182,10 +1111,7 @@ type oauthLoginParams struct {
1182
1111
// OrganizationSync has the organizations that the user will be assigned to.
1183
1112
OrganizationSync idpsync.OrganizationParams
1184
1113
GroupSync idpsync.GroupParams
1185
- // Is UsingRoles is true, then the user will be assigned
1186
- // the roles provided.
1187
- UsingRoles bool
1188
- Roles []string
1114
+ RoleSync idpsync.RoleParams
1189
1115
1190
1116
DebugContext OauthDebugContext
1191
1117
@@ -1394,37 +1320,10 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
1394
1320
return xerrors .Errorf ("sync groups: %w" , err )
1395
1321
}
1396
1322
1397
- // Ensure roles are correct.
1398
- if params .UsingRoles {
1399
- ignored := make ([]string , 0 )
1400
- filtered := make ([]string , 0 , len (params .Roles ))
1401
- for _ , role := range params .Roles {
1402
- // TODO: This only supports mapping deployment wide roles. Organization scoped roles
1403
- // are unsupported.
1404
- if _ , err := rbac .RoleByName (rbac.RoleIdentifier {Name : role }); err == nil {
1405
- filtered = append (filtered , role )
1406
- } else {
1407
- ignored = append (ignored , role )
1408
- }
1409
- }
1410
-
1411
- //nolint:gocritic
1412
- err := api .Options .SetUserSiteRoles (dbauthz .AsSystemRestricted (ctx ), logger , tx , user .ID , filtered )
1413
- if err != nil {
1414
- return & idpsync.HTTPError {
1415
- Code : http .StatusBadRequest ,
1416
- Msg : "Invalid roles through OIDC claims" ,
1417
- Detail : fmt .Sprintf ("Error from role assignment attempt: %s" , err .Error ()),
1418
- RenderStaticPage : true ,
1419
- }
1420
- }
1421
- if len (ignored ) > 0 {
1422
- logger .Debug (ctx , "OIDC roles ignored in assignment" ,
1423
- slog .F ("ignored" , ignored ),
1424
- slog .F ("assigned" , filtered ),
1425
- slog .F ("user_id" , user .ID ),
1426
- )
1427
- }
1323
+ // Role sync needs to occur after org sync.
1324
+ err = api .IDPSync .SyncRoles (ctx , tx , user , params .RoleSync )
1325
+ if err != nil {
1326
+ return xerrors .Errorf ("sync roles: %w" , err )
1428
1327
}
1429
1328
1430
1329
needsUpdate := false
0 commit comments