@@ -691,7 +691,7 @@ type OIDCConfig struct {
691
691
// UserRoleMapping controls how groups returned by the OIDC provider get mapped
692
692
// to roles within Coder.
693
693
// map[oidcRoleName]coderRoleName
694
- UserRoleMapping map [string ]string
694
+ UserRoleMapping map [string ][] string
695
695
// UserRolesDefault is the default set of roles to assign to a user if role sync
696
696
// is enabled.
697
697
UserRolesDefault []string
@@ -905,55 +905,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
905
905
}
906
906
}
907
907
908
- roles := api .OIDCConfig .UserRolesDefault
909
- if api .OIDCConfig .RoleSyncEnabled () {
910
- rolesRow , ok := claims [api .OIDCConfig .UserRoleField ]
911
- if ! ok {
912
- logger .Error (ctx , "oidc user roles are missing from claim" )
913
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
914
- Message : "Login disabled until OIDC config is fixed, contact your administrator. Missing OIDC user roles in claim." ,
915
- Detail : "If role sync is enabled, then the OIDC user roles must be present in the claim. Disabling role sync will allow login to proceed." ,
916
- })
917
- return
918
- }
919
-
920
- // Convert the []interface{} we get to a []string.
921
- rolesInterface , ok := rolesRow .([]interface {})
922
- if ! ok {
923
- api .Logger .Error (ctx , "oidc claim user roles field was an unknown type" ,
924
- slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
925
- )
926
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
927
- Message : "Login disabled until OIDC config is fixed, contact your administrator. Missing OIDC user roles in claim." ,
928
- Detail : fmt .Sprintf ("Roles claim must be an array of strings, type found: %T. Disabling role sync will allow login to proceed." , rolesRow ),
929
- })
930
- return
931
- }
932
-
933
- api .Logger .Debug (ctx , "roles returned in oidc claims" ,
934
- slog .F ("len" , len (rolesInterface )),
935
- slog .F ("roles" , rolesInterface ),
936
- )
937
- for _ , roleInterface := range rolesInterface {
938
- role , ok := roleInterface .(string )
939
- if ! ok {
940
- api .Logger .Error (ctx , "invalid oidc user role type" ,
941
- slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
942
- )
943
- httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
944
- Message : fmt .Sprintf ("Invalid user role type. Expected string, got: %T" , roleInterface ),
945
- })
946
- return
947
- }
948
-
949
- if mappedRole , ok := api .OIDCConfig .UserRoleMapping [role ]; ok {
950
- role = mappedRole
951
- }
952
-
953
- roles = append (roles , role )
954
- }
955
- }
956
-
957
908
// This conditional is purely to warn the user they might have misconfigured their OIDC
958
909
// configuration.
959
910
if _ , groupClaimExists := claims ["groups" ]; ! usingGroups && groupClaimExists {
@@ -1006,6 +957,63 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
1006
957
return
1007
958
}
1008
959
960
+ roles := api .OIDCConfig .UserRolesDefault
961
+ if api .OIDCConfig .RoleSyncEnabled () {
962
+ rolesRow , ok := claims [api .OIDCConfig .UserRoleField ]
963
+ if ! ok {
964
+ // If no claim is provided than we can assume the user is just
965
+ // a member. This is because there is no way to tell the difference
966
+ // between []string{} and nil for OIDC claims. IDPs omit claims
967
+ // if they are empty ([]string{}).
968
+ rolesRow = []string {}
969
+ }
970
+
971
+ // Convert the []interface{} we get to a []string.
972
+ rolesInterface , ok := rolesRow .([]interface {})
973
+ if ! ok {
974
+ api .Logger .Error (ctx , "oidc claim user roles field was an unknown type" ,
975
+ slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
976
+ )
977
+ site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
978
+ Status : http .StatusInternalServerError ,
979
+ HideStatus : true ,
980
+ Title : "Login disabled until OIDC config is fixed" ,
981
+ Description : fmt .Sprintf ("Roles claim must be an array of strings, type found: %T. Disabling role sync will allow login to proceed." , rolesRow ),
982
+ RetryEnabled : false ,
983
+ DashboardURL : "/login" ,
984
+ })
985
+ return
986
+ }
987
+
988
+ api .Logger .Debug (ctx , "roles returned in oidc claims" ,
989
+ slog .F ("len" , len (rolesInterface )),
990
+ slog .F ("roles" , rolesInterface ),
991
+ )
992
+ for _ , roleInterface := range rolesInterface {
993
+ role , ok := roleInterface .(string )
994
+ if ! ok {
995
+ api .Logger .Error (ctx , "invalid oidc user role type" ,
996
+ slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
997
+ )
998
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
999
+ Message : fmt .Sprintf ("Invalid user role type. Expected string, got: %T" , roleInterface ),
1000
+ })
1001
+ return
1002
+ }
1003
+
1004
+ if mappedRoles , ok := api .OIDCConfig .UserRoleMapping [role ]; ok {
1005
+ if len (mappedRoles ) == 0 {
1006
+ continue
1007
+ }
1008
+ // Mapped roles are added to the list of roles
1009
+ roles = append (roles , mappedRoles ... )
1010
+ continue
1011
+ }
1012
+
1013
+ roles = append (roles , role )
1014
+ }
1015
+ }
1016
+
1009
1017
// If a new user is authenticating for the first time
1010
1018
// the audit action is 'register', not 'login'
1011
1019
if user .ID == uuid .Nil {
@@ -1178,6 +1186,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
1178
1186
ctx = r .Context ()
1179
1187
user database.User
1180
1188
cookies []* http.Cookie
1189
+ logger = api .Logger .Named (userAuthLoggerName )
1181
1190
)
1182
1191
1183
1192
var isConvertLoginType bool
@@ -1320,10 +1329,32 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
1320
1329
1321
1330
// Ensure roles are correct.
1322
1331
if params .UsingRoles {
1332
+ ignored := make ([]string , 0 )
1333
+ filtered := make ([]string , 0 , len (params .Roles ))
1334
+ for _ , role := range params .Roles {
1335
+ if _ , err := rbac .RoleByName (role ); err == nil {
1336
+ filtered = append (filtered , role )
1337
+ } else {
1338
+ ignored = append (ignored , role )
1339
+ }
1340
+ }
1341
+
1323
1342
//nolint:gocritic
1324
- err := api .Options .SetUserSiteRoles (dbauthz .AsSystemRestricted (ctx ), tx , user .ID , params . Roles )
1343
+ err := api .Options .SetUserSiteRoles (dbauthz .AsSystemRestricted (ctx ), tx , user .ID , filtered )
1325
1344
if err != nil {
1326
- return xerrors .Errorf ("set user groups: %w" , err )
1345
+ return httpError {
1346
+ code : http .StatusBadRequest ,
1347
+ msg : "Invalid roles through OIDC claim" ,
1348
+ detail : fmt .Sprintf ("Error from role assignment attempt: %s" , err .Error ()),
1349
+ renderStaticPage : true ,
1350
+ }
1351
+ }
1352
+ if len (ignored ) > 0 {
1353
+ logger .Debug (ctx , "OIDC roles ignored in assignment" ,
1354
+ slog .F ("ignored" , ignored ),
1355
+ slog .F ("assigned" , filtered ),
1356
+ slog .F ("user_id" , user .ID ),
1357
+ )
1327
1358
}
1328
1359
}
1329
1360
0 commit comments