From eaead3216000a5d425ac79ca20bdf41392337f0a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 16 Sep 2024 13:07:35 -0500 Subject: [PATCH 1/2] feat: implement patch and get api methods for role sync --- coderd/idpsync/idpsync.go | 3 +- coderd/idpsync/role.go | 39 ++++++---- codersdk/idpsync.go | 37 ++++++++++ enterprise/coderd/coderd.go | 9 +++ enterprise/coderd/idpsync.go | 71 +++++++++++++++++- enterprise/coderd/idpsync_test.go | 119 ++++++++++++++++++++++++++++++ 6 files changed, 260 insertions(+), 18 deletions(-) diff --git a/coderd/idpsync/idpsync.go b/coderd/idpsync/idpsync.go index 41c0f7d3e8605..3f83c3e7a1501 100644 --- a/coderd/idpsync/idpsync.go +++ b/coderd/idpsync/idpsync.go @@ -54,7 +54,8 @@ type IDPSync interface { SiteRoleSyncEnabled() bool // RoleSyncSettings is similar to GroupSyncSettings. See GroupSyncSettings for // rational. - RoleSyncSettings() runtimeconfig.RuntimeEntry[*RoleSyncSettings] + RoleSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store) (*RoleSyncSettings, error) + UpdateRoleSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings RoleSyncSettings) error // ParseRoleClaims takes claims from an OIDC provider, and returns the params // for role syncing. Most of the logic happens in SyncRoles. ParseRoleClaims(ctx context.Context, mergedClaims jwt.MapClaims) (RoleParams, *HTTPError) diff --git a/coderd/idpsync/role.go b/coderd/idpsync/role.go index 9c63df157a764..cf768ee0eb05d 100644 --- a/coderd/idpsync/role.go +++ b/coderd/idpsync/role.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac/rolestore" "github.com/coder/coder/v2/coderd/runtimeconfig" "github.com/coder/coder/v2/coderd/util/slice" + "github.com/coder/coder/v2/codersdk" ) type RoleParams struct { @@ -41,8 +42,26 @@ func (AGPLIDPSync) SiteRoleSyncEnabled() bool { return false } -func (s AGPLIDPSync) RoleSyncSettings() runtimeconfig.RuntimeEntry[*RoleSyncSettings] { - return s.Role +func (s AGPLIDPSync) UpdateRoleSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings RoleSyncSettings) error { + orgResolver := s.Manager.OrganizationResolver(db, orgID) + err := s.SyncSettings.Role.SetRuntimeValue(ctx, orgResolver, &settings) + if err != nil { + return xerrors.Errorf("update role sync settings: %w", err) + } + + return nil +} + +func (s AGPLIDPSync) RoleSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store) (*RoleSyncSettings, error) { + rlv := s.Manager.OrganizationResolver(db, orgID) + settings, err := s.Role.Resolve(ctx, rlv) + if err != nil { + if !xerrors.Is(err, runtimeconfig.ErrEntryNotFound) { + return nil, xerrors.Errorf("resolve role sync settings: %w", err) + } + return &RoleSyncSettings{}, nil + } + return settings, nil } func (s AGPLIDPSync) ParseRoleClaims(_ context.Context, _ jwt.MapClaims) (RoleParams, *HTTPError) { @@ -85,15 +104,12 @@ func (s AGPLIDPSync) SyncRoles(ctx context.Context, db database.Store, user data allExpected := make([]rbac.RoleIdentifier, 0) for _, member := range orgMemberships { orgID := member.OrganizationMember.OrganizationID - orgResolver := s.Manager.OrganizationResolver(tx, orgID) - settings, err := s.RoleSyncSettings().Resolve(ctx, orgResolver) + settings, err := s.RoleSyncSettings(ctx, orgID, tx) if err != nil { - if !xerrors.Is(err, runtimeconfig.ErrEntryNotFound) { - return xerrors.Errorf("resolve group sync settings: %w", err) - } // No entry means no role syncing for this organization continue } + if settings.Field == "" { // Explicitly disabled role sync for this organization continue @@ -261,14 +277,7 @@ func (AGPLIDPSync) RolesFromClaim(field string, claims jwt.MapClaims) ([]string, return parsedRoles, nil } -type RoleSyncSettings struct { - // Field selects the claim field to be used as the created user's - // groups. If the group field is the empty string, then no group updates - // will ever come from the OIDC provider. - Field string `json:"field"` - // Mapping maps from an OIDC group --> Coder organization role - Mapping map[string][]string `json:"mapping"` -} +type RoleSyncSettings codersdk.RoleSyncSettings func (s *RoleSyncSettings) Set(v string) error { return json.Unmarshal([]byte(v), s) diff --git a/codersdk/idpsync.go b/codersdk/idpsync.go index 105efe57c578e..380b26336ad90 100644 --- a/codersdk/idpsync.go +++ b/codersdk/idpsync.go @@ -60,3 +60,40 @@ func (c *Client) PatchGroupIDPSyncSettings(ctx context.Context, orgID string, re var resp GroupSyncSettings return resp, json.NewDecoder(res.Body).Decode(&resp) } + +type RoleSyncSettings struct { + // Field selects the claim field to be used as the created user's + // groups. If the group field is the empty string, then no group updates + // will ever come from the OIDC provider. + Field string `json:"field"` + // Mapping maps from an OIDC group --> Coder organization role + Mapping map[string][]string `json:"mapping"` +} + +func (c *Client) RoleIDPSyncSettings(ctx context.Context, orgID string) (RoleSyncSettings, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/roles", orgID), nil) + if err != nil { + return RoleSyncSettings{}, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return RoleSyncSettings{}, ReadBodyAsError(res) + } + var resp RoleSyncSettings + return resp, json.NewDecoder(res.Body).Decode(&resp) +} + +func (c *Client) PatchRoleIDPSyncSettings(ctx context.Context, orgID string, req RoleSyncSettings) (RoleSyncSettings, error) { + res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/roles", orgID), req) + if err != nil { + return RoleSyncSettings{}, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return RoleSyncSettings{}, ReadBodyAsError(res) + } + var resp RoleSyncSettings + return resp, json.NewDecoder(res.Body).Decode(&resp) +} diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 704964e5fcf97..c030441253f88 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -286,9 +286,18 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { r.Post("/organizations/{organization}/members/roles", api.postOrgRoles) r.Put("/organizations/{organization}/members/roles", api.putOrgRoles) r.Delete("/organizations/{organization}/members/roles/{roleName}", api.deleteOrgRole) + }) + + r.Group(func(r chi.Router) { + r.Use( + apiKeyMiddleware, + httpmw.ExtractOrganizationParam(api.Database), + ) r.Route("/organizations/{organization}/settings", func(r chi.Router) { r.Get("/idpsync/groups", api.groupIDPSyncSettings) r.Patch("/idpsync/groups", api.patchGroupIDPSyncSettings) + r.Get("/idpsync/roles", api.roleIDPSyncSettings) + r.Patch("/idpsync/roles", api.patchRoleIDPSyncSettings) }) }) diff --git a/enterprise/coderd/idpsync.go b/enterprise/coderd/idpsync.go index 22781856ce08a..f5b3b6f0130d4 100644 --- a/enterprise/coderd/idpsync.go +++ b/enterprise/coderd/idpsync.go @@ -17,7 +17,7 @@ import ( // @Produce json // @Tags Enterprise // @Param organization path string true "Organization ID" format(uuid) -// @Success 200 {object} idpsync.GroupSyncSettings +// @Success 200 {object} codersdk.GroupSyncSettings // @Router /organizations/{organization}/settings/idpsync/groups [get] func (api *API) groupIDPSyncSettings(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -45,7 +45,7 @@ func (api *API) groupIDPSyncSettings(rw http.ResponseWriter, r *http.Request) { // @Produce json // @Tags Enterprise // @Param organization path string true "Organization ID" format(uuid) -// @Success 200 {object} idpsync.GroupSyncSettings +// @Success 200 {object} codersdk.GroupSyncSettings // @Router /organizations/{organization}/settings/idpsync/groups [patch] func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -77,3 +77,70 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques httpapi.Write(ctx, rw, http.StatusOK, settings) } + +// @Summary Get role IdP Sync settings by organization +// @ID get-role-idp-sync-settings-by-organization +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param organization path string true "Organization ID" format(uuid) +// @Success 200 {object} codersdk.RoleSyncSettings +// @Router /organizations/{organization}/settings/idpsync/roles [get] +func (api *API) roleIDPSyncSettings(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + org := httpmw.OrganizationParam(r) + + if !api.Authorize(r, policy.ActionRead, rbac.ResourceIdpsyncSettings.InOrg(org.ID)) { + httpapi.Forbidden(rw) + return + } + + //nolint:gocritic // Requires system context to read runtime config + sysCtx := dbauthz.AsSystemRestricted(ctx) + settings, err := api.IDPSync.RoleSyncSettings(sysCtx, org.ID, api.Database) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusOK, settings) +} + +// @Summary Update role IdP Sync settings by organization +// @ID update-role-idp-sync-settings-by-organization +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param organization path string true "Organization ID" format(uuid) +// @Success 200 {object} codersdk.RoleSyncSettings +// @Router /organizations/{organization}/settings/idpsync/roles [patch] +func (api *API) patchRoleIDPSyncSettings(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + org := httpmw.OrganizationParam(r) + + if !api.Authorize(r, policy.ActionUpdate, rbac.ResourceIdpsyncSettings.InOrg(org.ID)) { + httpapi.Forbidden(rw) + return + } + + var req idpsync.RoleSyncSettings + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + //nolint:gocritic // Requires system context to update runtime config + sysCtx := dbauthz.AsSystemRestricted(ctx) + err := api.IDPSync.UpdateRoleSettings(sysCtx, org.ID, api.Database, req) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + settings, err := api.IDPSync.RoleSyncSettings(sysCtx, org.ID, api.Database) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusOK, settings) +} diff --git a/enterprise/coderd/idpsync_test.go b/enterprise/coderd/idpsync_test.go index 2dc236516d8ca..374e318d23793 100644 --- a/enterprise/coderd/idpsync_test.go +++ b/enterprise/coderd/idpsync_test.go @@ -170,3 +170,122 @@ func TestPostGroupSyncConfig(t *testing.T) { require.Equal(t, http.StatusForbidden, apiError.StatusCode()) }) } + +func TestGetRoleSyncConfig(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentCustomRoles), + string(codersdk.ExperimentMultiOrganization), + } + + owner, _, _, user := coderdenttest.NewWithAPI(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureCustomRoles: 1, + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.ScopedRoleOrgAdmin(user.OrganizationID)) + + ctx := testutil.Context(t, testutil.WaitShort) + settings, err := orgAdmin.PatchRoleIDPSyncSettings(ctx, user.OrganizationID.String(), codersdk.RoleSyncSettings{ + Field: "august", + Mapping: map[string][]string{ + "foo": {"bar"}, + }, + }) + require.NoError(t, err) + require.Equal(t, "august", settings.Field) + require.Equal(t, map[string][]string{"foo": {"bar"}}, settings.Mapping) + + settings, err = orgAdmin.RoleIDPSyncSettings(ctx, user.OrganizationID.String()) + require.NoError(t, err) + require.Equal(t, "august", settings.Field) + require.Equal(t, map[string][]string{"foo": {"bar"}}, settings.Mapping) + }) +} + +func TestPostRoleSyncConfig(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentCustomRoles), + string(codersdk.ExperimentMultiOrganization), + } + + owner, user := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureCustomRoles: 1, + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + + orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.ScopedRoleOrgAdmin(user.OrganizationID)) + + // Test as org admin + ctx := testutil.Context(t, testutil.WaitShort) + settings, err := orgAdmin.PatchRoleIDPSyncSettings(ctx, user.OrganizationID.String(), codersdk.RoleSyncSettings{ + Field: "august", + }) + require.NoError(t, err) + require.Equal(t, "august", settings.Field) + + fetchedSettings, err := orgAdmin.RoleIDPSyncSettings(ctx, user.OrganizationID.String()) + require.NoError(t, err) + require.Equal(t, "august", fetchedSettings.Field) + }) + + t.Run("NotAuthorized", func(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.Experiments = []string{ + string(codersdk.ExperimentCustomRoles), + string(codersdk.ExperimentMultiOrganization), + } + + owner, user := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureCustomRoles: 1, + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + + member, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID) + + ctx := testutil.Context(t, testutil.WaitShort) + _, err := member.PatchRoleIDPSyncSettings(ctx, user.OrganizationID.String(), codersdk.RoleSyncSettings{ + Field: "august", + }) + var apiError *codersdk.Error + require.ErrorAs(t, err, &apiError) + require.Equal(t, http.StatusForbidden, apiError.StatusCode()) + + _, err = member.RoleIDPSyncSettings(ctx, user.OrganizationID.String()) + require.ErrorAs(t, err, &apiError) + require.Equal(t, http.StatusForbidden, apiError.StatusCode()) + }) +} From 2c9420db72e3c38ffcfadc747617b0c3af21cb23 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 16 Sep 2024 13:31:27 -0500 Subject: [PATCH 2/2] make gen --- coderd/apidoc/docs.go | 167 +++++++++++++++++++++++-------- coderd/apidoc/swagger.json | 159 +++++++++++++++++++++-------- docs/reference/api/enterprise.md | 90 ++++++++++++++++- docs/reference/api/schemas.md | 80 +++++++++------ site/src/api/typesGenerated.ts | 6 ++ 5 files changed, 388 insertions(+), 114 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 12a7321189806..996f3b2f2b98d 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -3155,7 +3155,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/idpsync.GroupSyncSettings" + "$ref": "#/definitions/codersdk.GroupSyncSettings" } } } @@ -3188,7 +3188,75 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/idpsync.GroupSyncSettings" + "$ref": "#/definitions/codersdk.GroupSyncSettings" + } + } + } + } + }, + "/organizations/{organization}/settings/idpsync/roles": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get role IdP Sync settings by organization", + "operationId": "get-role-idp-sync-settings-by-organization", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.RoleSyncSettings" + } + } + } + }, + "patch": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Update role IdP Sync settings by organization", + "operationId": "update-role-idp-sync-settings-by-organization", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.RoleSyncSettings" } } } @@ -10523,6 +10591,44 @@ const docTemplate = `{ "GroupSourceOIDC" ] }, + "codersdk.GroupSyncSettings": { + "type": "object", + "properties": { + "auto_create_missing_groups": { + "description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.", + "type": "boolean" + }, + "field": { + "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", + "type": "string" + }, + "legacy_group_name_mapping": { + "description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mapping": { + "description": "Mapping maps from an OIDC group --\u003e Coder group ID", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "regex_filter": { + "description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.", + "allOf": [ + { + "$ref": "#/definitions/regexp.Regexp" + } + ] + } + } + }, "codersdk.Healthcheck": { "type": "object", "properties": { @@ -12238,6 +12344,25 @@ const docTemplate = `{ } } }, + "codersdk.RoleSyncSettings": { + "type": "object", + "properties": { + "field": { + "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", + "type": "string" + }, + "mapping": { + "description": "Mapping maps from an OIDC group --\u003e Coder organization role", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, "codersdk.SSHConfig": { "type": "object", "properties": { @@ -15253,44 +15378,6 @@ const docTemplate = `{ } } }, - "idpsync.GroupSyncSettings": { - "type": "object", - "properties": { - "auto_create_missing_groups": { - "description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.", - "type": "boolean" - }, - "field": { - "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", - "type": "string" - }, - "legacy_group_name_mapping": { - "description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "mapping": { - "description": "Mapping maps from an OIDC group --\u003e Coder group ID", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "regex_filter": { - "description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.", - "allOf": [ - { - "$ref": "#/definitions/regexp.Regexp" - } - ] - } - } - }, "key.NodePublic": { "type": "object" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 23a1f369c5181..fa9288714fc82 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -2773,7 +2773,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/idpsync.GroupSyncSettings" + "$ref": "#/definitions/codersdk.GroupSyncSettings" } } } @@ -2802,7 +2802,67 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/idpsync.GroupSyncSettings" + "$ref": "#/definitions/codersdk.GroupSyncSettings" + } + } + } + } + }, + "/organizations/{organization}/settings/idpsync/roles": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get role IdP Sync settings by organization", + "operationId": "get-role-idp-sync-settings-by-organization", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.RoleSyncSettings" + } + } + } + }, + "patch": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Update role IdP Sync settings by organization", + "operationId": "update-role-idp-sync-settings-by-organization", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.RoleSyncSettings" } } } @@ -9445,6 +9505,44 @@ "enum": ["user", "oidc"], "x-enum-varnames": ["GroupSourceUser", "GroupSourceOIDC"] }, + "codersdk.GroupSyncSettings": { + "type": "object", + "properties": { + "auto_create_missing_groups": { + "description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.", + "type": "boolean" + }, + "field": { + "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", + "type": "string" + }, + "legacy_group_name_mapping": { + "description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "mapping": { + "description": "Mapping maps from an OIDC group --\u003e Coder group ID", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "regex_filter": { + "description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.", + "allOf": [ + { + "$ref": "#/definitions/regexp.Regexp" + } + ] + } + } + }, "codersdk.Healthcheck": { "type": "object", "properties": { @@ -11070,6 +11168,25 @@ } } }, + "codersdk.RoleSyncSettings": { + "type": "object", + "properties": { + "field": { + "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", + "type": "string" + }, + "mapping": { + "description": "Mapping maps from an OIDC group --\u003e Coder organization role", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, "codersdk.SSHConfig": { "type": "object", "properties": { @@ -13906,44 +14023,6 @@ } } }, - "idpsync.GroupSyncSettings": { - "type": "object", - "properties": { - "auto_create_missing_groups": { - "description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.", - "type": "boolean" - }, - "field": { - "description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.", - "type": "string" - }, - "legacy_group_name_mapping": { - "description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "mapping": { - "description": "Mapping maps from an OIDC group --\u003e Coder group ID", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "regex_filter": { - "description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.", - "allOf": [ - { - "$ref": "#/definitions/regexp.Regexp" - } - ] - } - } - }, "key.NodePublic": { "type": "object" }, diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md index 555acc4af8552..d7f9a28803d73 100644 --- a/docs/reference/api/enterprise.md +++ b/docs/reference/api/enterprise.md @@ -1817,9 +1817,9 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/setting ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [idpsync.GroupSyncSettings](schemas.md#idpsyncgroupsyncsettings) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GroupSyncSettings](schemas.md#codersdkgroupsyncsettings) | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -1864,9 +1864,91 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/setti ### Responses +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.GroupSyncSettings](schemas.md#codersdkgroupsyncsettings) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Get role IdP Sync settings by organization + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/roles \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations/{organization}/settings/idpsync/roles` + +### Parameters + +| Name | In | Type | Required | Description | +| -------------- | ---- | ------------ | -------- | --------------- | +| `organization` | path | string(uuid) | true | Organization ID | + +### Example responses + +> 200 Response + +```json +{ + "field": "string", + "mapping": { + "property1": ["string"], + "property2": ["string"] + } +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.RoleSyncSettings](schemas.md#codersdkrolesyncsettings) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + +## Update role IdP Sync settings by organization + +### Code samples + +```shell +# Example request using curl +curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/roles \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`PATCH /organizations/{organization}/settings/idpsync/roles` + +### Parameters + +| Name | In | Type | Required | Description | +| -------------- | ---- | ------------ | -------- | --------------- | +| `organization` | path | string(uuid) | true | Organization ID | + +### Example responses + +> 200 Response + +```json +{ + "field": "string", + "mapping": { + "property1": ["string"], + "property2": ["string"] + } +} +``` + +### Responses + | Status | Meaning | Description | Schema | | ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [idpsync.GroupSyncSettings](schemas.md#idpsyncgroupsyncsettings) | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.RoleSyncSettings](schemas.md#codersdkrolesyncsettings) | To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 979779e70a296..6377707bdfb4e 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -2895,6 +2895,36 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `user` | | `oidc` | +## codersdk.GroupSyncSettings + +```json +{ + "auto_create_missing_groups": true, + "field": "string", + "legacy_group_name_mapping": { + "property1": "string", + "property2": "string" + }, + "mapping": { + "property1": ["string"], + "property2": ["string"] + }, + "regex_filter": {} +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------------------- | ------------------------------ | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `auto_create_missing_groups` | boolean | false | | Auto create missing groups controls whether groups returned by the OIDC provider are automatically created in Coder if they are missing. | +| `field` | string | false | | Field selects the claim field to be used as the created user's groups. If the group field is the empty string, then no group updates will ever come from the OIDC provider. | +| `legacy_group_name_mapping` | object | false | | Legacy group name mapping is deprecated. It remaps an IDP group name to a Coder group name. Since configuration is now done at runtime, group IDs are used to account for group renames. For legacy configurations, this config option has to remain. Deprecated: Use Mapping instead. | +| » `[any property]` | string | false | | | +| `mapping` | object | false | | Mapping maps from an OIDC group --> Coder group ID | +| » `[any property]` | array of string | false | | | +| `regex_filter` | [regexp.Regexp](#regexpregexp) | false | | Regex filter is a regular expression that filters the groups returned by the OIDC provider. Any group not matched by this regex will be ignored. If the group filter is nil, then no group filtering will occur. | + ## codersdk.Healthcheck ```json @@ -4660,6 +4690,26 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `site_permissions` | array of [codersdk.Permission](#codersdkpermission) | false | | | | `user_permissions` | array of [codersdk.Permission](#codersdkpermission) | false | | | +## codersdk.RoleSyncSettings + +```json +{ + "field": "string", + "mapping": { + "property1": ["string"], + "property2": ["string"] + } +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------ | --------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `field` | string | false | | Field selects the claim field to be used as the created user's groups. If the group field is the empty string, then no group updates will ever come from the OIDC provider. | +| `mapping` | object | false | | Mapping maps from an OIDC group --> Coder organization role | +| » `[any property]` | array of string | false | | | + ## codersdk.SSHConfig ```json @@ -8964,36 +9014,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `severity` | `warning` | | `severity` | `error` | -## idpsync.GroupSyncSettings - -```json -{ - "auto_create_missing_groups": true, - "field": "string", - "legacy_group_name_mapping": { - "property1": "string", - "property2": "string" - }, - "mapping": { - "property1": ["string"], - "property2": ["string"] - }, - "regex_filter": {} -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ---------------------------- | ------------------------------ | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `auto_create_missing_groups` | boolean | false | | Auto create missing groups controls whether groups returned by the OIDC provider are automatically created in Coder if they are missing. | -| `field` | string | false | | Field selects the claim field to be used as the created user's groups. If the group field is the empty string, then no group updates will ever come from the OIDC provider. | -| `legacy_group_name_mapping` | object | false | | Legacy group name mapping is deprecated. It remaps an IDP group name to a Coder group name. Since configuration is now done at runtime, group IDs are used to account for group renames. For legacy configurations, this config option has to remain. Deprecated: Use Mapping instead. | -| » `[any property]` | string | false | | | -| `mapping` | object | false | | Mapping maps from an OIDC group --> Coder group ID | -| » `[any property]` | array of string | false | | | -| `regex_filter` | [regexp.Regexp](#regexpregexp) | false | | Regex filter is a regular expression that filters the groups returned by the OIDC provider. Any group not matched by this regex will be ignored. If the group filter is nil, then no group filtering will occur. | - ## key.NodePublic ```json diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7c5785cbc5c56..8e94471b9cc60 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1164,6 +1164,12 @@ export interface Role { readonly user_permissions: Readonly>; } +// From codersdk/idpsync.go +export interface RoleSyncSettings { + readonly field: string; + readonly mapping: Record>>; +} + // From codersdk/deployment.go export interface SSHConfig { readonly DeploymentName: string;