@@ -740,17 +740,6 @@ type OIDCConfig struct {
740740// support the userinfo endpoint, or if the userinfo endpoint causes
741741// undesirable behavior.
742742IgnoreUserInfo 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
754743// SignInText is the text to display on the OIDC login button
755744SignInText string
756745// IconURL points to the URL of an icon to display on the OIDC login button
@@ -759,10 +748,6 @@ type OIDCConfig struct {
759748SignupsDisabledText string
760749}
761750
762- func (cfg OIDCConfig )RoleSyncEnabled ()bool {
763- return cfg .UserRoleField != ""
764- }
765-
766751// @Summary OpenID Connect Callback
767752// @ID openid-connect-callback
768753// @Security CoderSessionToken
@@ -983,12 +968,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
983968
984969ctx = slog .With (ctx ,slog .F ("email" ,email ),slog .F ("username" ,username ),slog .F ("name" ,name ))
985970
986- roles ,roleErr := api .oidcRoles (ctx ,mergedClaims )
987- if roleErr != nil {
988- roleErr .Write (rw ,r )
989- return
990- }
991-
992971user ,link ,err := findLinkedUser (ctx ,api .Database ,oidcLinkedID (idToken ),email )
993972if err != nil {
994973logger .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) {
1011990return
1012991}
1013992
993+ roleSync ,roleSyncErr := api .IDPSync .ParseRoleClaims (ctx ,mergedClaims )
994+ if roleSyncErr != nil {
995+ roleSyncErr .Write (rw ,r )
996+ return
997+ }
998+
1014999// If a new user is authenticating for the first time
10151000// the audit action is 'register', not 'login'
10161001if user .ID == uuid .Nil {
@@ -1028,10 +1013,9 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
10281013Username :username ,
10291014Name :name ,
10301015AvatarURL :picture ,
1031- UsingRoles :api .OIDCConfig .RoleSyncEnabled (),
1032- Roles :roles ,
10331016OrganizationSync :orgSync ,
10341017GroupSync :groupSync ,
1018+ RoleSync :roleSync ,
10351019DebugContext :OauthDebugContext {
10361020IDTokenClaims :idtokenClaims ,
10371021UserInfoClaims :userInfoClaims ,
@@ -1067,61 +1051,6 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
10671051http .Redirect (rw ,r ,redirect ,http .StatusTemporaryRedirect )
10681052}
10691053
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-
11251054// claimFields returns the sorted list of fields in the claims map.
11261055func claimFields (claims map [string ]interface {}) []string {
11271056fields := []string {}
@@ -1182,10 +1111,7 @@ type oauthLoginParams struct {
11821111// OrganizationSync has the organizations that the user will be assigned to.
11831112OrganizationSync idpsync.OrganizationParams
11841113GroupSync 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
11891115
11901116DebugContext OauthDebugContext
11911117
@@ -1394,37 +1320,10 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
13941320return xerrors .Errorf ("sync groups: %w" ,err )
13951321}
13961322
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 )
14281327}
14291328
14301329needsUpdate := false