@@ -1019,31 +1019,26 @@ func (api *API) oidcGroups(ctx context.Context, mergedClaims map[string]interfac
1019
1019
if api .OIDCConfig .GroupField != "" {
1020
1020
usingGroups = true
1021
1021
groupsRaw , ok := mergedClaims [api .OIDCConfig .GroupField ]
1022
- if ok && api .OIDCConfig .GroupField != "" {
1023
- // Convert the []interface{} we get to a []string.
1024
- groupsInterface , ok := groupsRaw .([]interface {})
1025
- if ok {
1026
- api .Logger .Debug (ctx , "groups returned in oidc claims" ,
1027
- slog .F ("len" , len (groupsInterface )),
1028
- slog .F ("groups" , groupsInterface ),
1022
+ if ok {
1023
+ parsedGroups , err := parseStringSliceClaim (groupsRaw )
1024
+ if err != nil {
1025
+ api .Logger .Debug (ctx , "groups field was an unknown type in oidc claims" ,
1026
+ slog .F ("type" , fmt .Sprintf ("%T" , groupsRaw )),
1027
+ slog .Error (err ),
1029
1028
)
1029
+ return false , nil , err
1030
+ }
1030
1031
1031
- for _ , groupInterface := range groupsInterface {
1032
- group , ok := groupInterface .(string )
1033
- if ! ok {
1034
- return false , nil , xerrors .Errorf ("Invalid group type. Expected string, got: %T" , groupInterface )
1035
- }
1036
-
1037
- if mappedGroup , ok := api .OIDCConfig .GroupMapping [group ]; ok {
1038
- group = mappedGroup
1039
- }
1032
+ api .Logger .Debug (ctx , "groups returned in oidc claims" ,
1033
+ slog .F ("len" , len (parsedGroups )),
1034
+ slog .F ("groups" , parsedGroups ),
1035
+ )
1040
1036
1041
- groups = append (groups , group )
1037
+ for _ , group := range parsedGroups {
1038
+ if mappedGroup , ok := api .OIDCConfig .GroupMapping [group ]; ok {
1039
+ group = mappedGroup
1042
1040
}
1043
- } else {
1044
- api .Logger .Debug (ctx , "groups field was an unknown type" ,
1045
- slog .F ("type" , fmt .Sprintf ("%T" , groupsRaw )),
1046
- )
1041
+ groups = append (groups , group )
1047
1042
}
1048
1043
}
1049
1044
}
@@ -1079,10 +1074,11 @@ func (api *API) oidcRoles(ctx context.Context, rw http.ResponseWriter, r *http.R
1079
1074
rolesRow = []interface {}{}
1080
1075
}
1081
1076
1082
- rolesInterface , ok := rolesRow .([] interface {} )
1083
- if ! ok {
1084
- api .Logger .Error (ctx , "oidc claim user roles field was an unknown type" ,
1077
+ parsedRoles , err := parseStringSliceClaim ( rolesRow )
1078
+ if err != nil {
1079
+ api .Logger .Error (ctx , "oidc claims user roles field was an unknown type" ,
1085
1080
slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
1081
+ slog .Error (err ),
1086
1082
)
1087
1083
site .RenderStaticErrorPage (rw , r , site.ErrorPageData {
1088
1084
Status : http .StatusInternalServerError ,
@@ -1096,21 +1092,10 @@ func (api *API) oidcRoles(ctx context.Context, rw http.ResponseWriter, r *http.R
1096
1092
}
1097
1093
1098
1094
api .Logger .Debug (ctx , "roles returned in oidc claims" ,
1099
- slog .F ("len" , len (rolesInterface )),
1100
- slog .F ("roles" , rolesInterface ),
1095
+ slog .F ("len" , len (parsedRoles )),
1096
+ slog .F ("roles" , parsedRoles ),
1101
1097
)
1102
- for _ , roleInterface := range rolesInterface {
1103
- role , ok := roleInterface .(string )
1104
- if ! ok {
1105
- api .Logger .Error (ctx , "invalid oidc user role type" ,
1106
- slog .F ("type" , fmt .Sprintf ("%T" , rolesRow )),
1107
- )
1108
- httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
1109
- Message : fmt .Sprintf ("Invalid user role type. Expected string, got: %T" , roleInterface ),
1110
- })
1111
- return nil , false
1112
- }
1113
-
1098
+ for _ , role := range parsedRoles {
1114
1099
if mappedRoles , ok := api .OIDCConfig .UserRoleMapping [role ]; ok {
1115
1100
if len (mappedRoles ) == 0 {
1116
1101
continue
@@ -1449,7 +1434,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
1449
1434
if err != nil {
1450
1435
return httpError {
1451
1436
code : http .StatusBadRequest ,
1452
- msg : "Invalid roles through OIDC claim " ,
1437
+ msg : "Invalid roles through OIDC claims " ,
1453
1438
detail : fmt .Sprintf ("Error from role assignment attempt: %s" , err .Error ()),
1454
1439
renderStaticPage : true ,
1455
1440
}
@@ -1744,3 +1729,50 @@ func wrongLoginTypeHTTPError(user database.LoginType, params database.LoginType)
1744
1729
params , user , addedMsg ),
1745
1730
}
1746
1731
}
1732
+
1733
+ // parseStringSliceClaim parses the claim for groups and roles, expected []string.
1734
+ //
1735
+ // Some providers like ADFS return a single string instead of an array if there
1736
+ // is only 1 element. So this function handles the edge cases.
1737
+ func parseStringSliceClaim (claim interface {}) ([]string , error ) {
1738
+ groups := make ([]string , 0 )
1739
+ if claim == nil {
1740
+ return groups , nil
1741
+ }
1742
+
1743
+ // The simple case is the type is exactly what we expected
1744
+ asStringArray , ok := claim .([]string )
1745
+ if ok {
1746
+ return asStringArray , nil
1747
+ }
1748
+
1749
+ asArray , ok := claim .([]interface {})
1750
+ if ok {
1751
+ for i , item := range asArray {
1752
+ asString , ok := item .(string )
1753
+ if ! ok {
1754
+ return nil , xerrors .Errorf ("invalid claim type. Element %d expected a string, got: %T" , i , item )
1755
+ }
1756
+ groups = append (groups , asString )
1757
+ }
1758
+ return groups , nil
1759
+ }
1760
+
1761
+ asString , ok := claim .(string )
1762
+ if ok {
1763
+ if asString == "" {
1764
+ // Empty string should be 0 groups.
1765
+ return []string {}, nil
1766
+ }
1767
+ // If it is a single string, first check if it is a csv.
1768
+ // If a user hits this, it is likely a misconfiguration and they need
1769
+ // to reconfigure their IDP to send an array instead.
1770
+ if strings .Contains (asString , "," ) {
1771
+ return nil , xerrors .Errorf ("invalid claim type. Got a csv string (%q), change this claim to return an array of strings instead." , asString )
1772
+ }
1773
+ return []string {asString }, nil
1774
+ }
1775
+
1776
+ // Not sure what the user gave us.
1777
+ return nil , xerrors .Errorf ("invalid claim type. Expected an array of strings, got: %T" , claim )
1778
+ }
0 commit comments