Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
role sync patching + codersdk
  • Loading branch information
aslilac committed Jan 30, 2025
commit 0456644478d0e10b31e88308eb1a3cb845404bc4
2 changes: 1 addition & 1 deletion coderd/idpsync/idpsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type IDPSync interface {
// RoleSyncSettings is similar to GroupSyncSettings. See GroupSyncSettings for
// rational.
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
UpdateRoleSyncSettings(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)
Expand Down
2 changes: 1 addition & 1 deletion coderd/idpsync/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (AGPLIDPSync) SiteRoleSyncEnabled() bool {
return false
}

func (s AGPLIDPSync) UpdateRoleSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings RoleSyncSettings) error {
func (s AGPLIDPSync) UpdateRoleSyncSettings(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 {
Expand Down
78 changes: 78 additions & 0 deletions codersdk/idpsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,46 @@ func (c *Client) PatchGroupIDPSyncSettings(ctx context.Context, orgID string, re
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

type PatchGroupIDPSyncConfigRequest struct {
Field string `json:"field"`
RegexFilter *regexp.Regexp `json:"regex_filter"`
AutoCreateMissing bool `json:"auto_create_missing_groups"`
}

func (c *Client) PatchGroupIDPSyncConfig(ctx context.Context, orgID string, req PatchGroupIDPSyncConfigRequest) (GroupSyncSettings, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/groups/config", orgID), req)
if err != nil {
return GroupSyncSettings{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return GroupSyncSettings{}, ReadBodyAsError(res)
}
var resp GroupSyncSettings
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

// If the same mapping is present in both Add and Remove, Remove will take presidence.
type PatchGroupIDPSyncMappingRequest struct {
Add []IDPSyncMapping[uuid.UUID]
Remove []IDPSyncMapping[uuid.UUID]
}

func (c *Client) PatchGroupIDPSyncMapping(ctx context.Context, orgID string, req PatchGroupIDPSyncMappingRequest) (GroupSyncSettings, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/groups/mapping", orgID), req)
if err != nil {
return GroupSyncSettings{}, xerrors.Errorf("make request: %w", err)
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return GroupSyncSettings{}, ReadBodyAsError(res)
}
var resp GroupSyncSettings
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

type RoleSyncSettings struct {
// Field is the name of the claim field that specifies what organization roles
// a user should be given. If empty, no roles will be synced.
Expand Down Expand Up @@ -104,6 +144,44 @@ func (c *Client) PatchRoleIDPSyncSettings(ctx context.Context, orgID string, req
return resp, json.NewDecoder(res.Body).Decode(&resp)
}

type PatchRoleIDPSyncConfigRequest struct {
Field string `json:"field"`
}

func (c *Client) PatchRoleIDPSyncConfig(ctx context.Context, orgID string, req PatchGroupIDPSyncConfigRequest) (RoleSyncSettings, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/roles/config", 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)
}

// If the same mapping is present in both Add and Remove, Remove will take presidence.
type PatchRoleIDPSyncMappingRequest struct {
Add []IDPSyncMapping[string]
Remove []IDPSyncMapping[string]
}

func (c *Client) PatchRoleIDPSyncMapping(ctx context.Context, orgID string, req PatchGroupIDPSyncMappingRequest) (RoleSyncSettings, error) {
res, err := c.Request(ctx, http.MethodPatch, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/roles/mapping", 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)
}

type OrganizationSyncSettings struct {
// Field selects the claim field to be used as the created user's
// organizations. If the field is the empty string, then no organization
Expand Down
2 changes: 2 additions & 0 deletions enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {

r.Get("/idpsync/roles", api.roleIDPSyncSettings)
r.Patch("/idpsync/roles", api.patchRoleIDPSyncSettings)
r.Patch("/idpsync/roles/config", api.patchRoleIDPSyncConfig)
r.Patch("/idpsync/roles/mapping", api.patchRoleIDPSyncMapping)

r.Get("/idpsync/available-fields", api.organizationIDPSyncClaimFields)
r.Get("/idpsync/field-values", api.organizationIDPSyncClaimFieldValues)
Expand Down
135 changes: 134 additions & 1 deletion enterprise/coderd/idpsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func (api *API) patchRoleIDPSyncSettings(rw http.ResponseWriter, r *http.Request
}
aReq.Old = *existing

err = api.IDPSync.UpdateRoleSettings(sysCtx, org.ID, api.Database, idpsync.RoleSyncSettings{
err = api.IDPSync.UpdateRoleSyncSettings(sysCtx, org.ID, api.Database, idpsync.RoleSyncSettings{
Field: req.Field,
Mapping: req.Mapping,
})
Expand All @@ -369,6 +369,139 @@ func (api *API) patchRoleIDPSyncSettings(rw http.ResponseWriter, r *http.Request
})
}

// @Summary Update role IdP Sync config
// @ID update-role-idp-sync-config
// @Security CoderSessionToken
// @Produce json
// @Accept json
// @Tags Enterprise
// @Success 200 {object} codersdk.RoleSyncSettings
// @Param request body codersdk.PatchRoleIDPSyncConfigRequest true "New config values"
// @Router /organizations/{organization}/settings/idpsync/roles/config [patch]
func (api *API) patchRoleIDPSyncConfig(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
org := httpmw.OrganizationParam(r)
auditor := *api.AGPL.Auditor.Load()
aReq, commitAudit := audit.InitRequest[idpsync.RoleSyncSettings](rw, &audit.RequestParams{
Audit: auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionWrite,
OrganizationID: org.ID,
})
defer commitAudit()

if !api.Authorize(r, policy.ActionUpdate, rbac.ResourceIdpsyncSettings.InOrg(org.ID)) {
httpapi.Forbidden(rw)
return
}

var req codersdk.PatchRoleIDPSyncConfigRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}

var settings idpsync.RoleSyncSettings
//nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx)
err := database.ReadModifyUpdate(api.Database, func(tx database.Store) error {
existing, err := api.IDPSync.RoleSyncSettings(sysCtx, org.ID, tx)
if err != nil {
return err
}
aReq.Old = *existing

settings = idpsync.RoleSyncSettings{
Field: req.Field,
Mapping: existing.Mapping,
}

err = api.IDPSync.UpdateRoleSyncSettings(sysCtx, org.ID, tx, settings)
if err != nil {
return err
}

return nil
})
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

aReq.New = settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.RoleSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
})
}

// @Summary Update role IdP Sync mapping
// @ID update-role-idp-sync-mapping
// @Security CoderSessionToken
// @Produce json
// @Accept json
// @Tags Enterprise
// @Success 200 {object} codersdk.RoleSyncSettings
// @Param request body codersdk.PatchRoleIDPSyncMappingRequest true "Description of the mappings to add and remove"
// @Router /organizations/{organization}/settings/idpsync/roles/mapping [patch]
func (api *API) patchRoleIDPSyncMapping(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
org := httpmw.OrganizationParam(r)
auditor := *api.AGPL.Auditor.Load()
aReq, commitAudit := audit.InitRequest[idpsync.RoleSyncSettings](rw, &audit.RequestParams{
Audit: auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionWrite,
OrganizationID: org.ID,
})
defer commitAudit()

if !api.Authorize(r, policy.ActionUpdate, rbac.ResourceIdpsyncSettings.InOrg(org.ID)) {
httpapi.Forbidden(rw)
return
}

var req codersdk.PatchRoleIDPSyncMappingRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}

var settings idpsync.RoleSyncSettings
//nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx)
err := database.ReadModifyUpdate(api.Database, func(tx database.Store) error {
existing, err := api.IDPSync.RoleSyncSettings(sysCtx, org.ID, tx)
if err != nil {
return err
}
aReq.Old = *existing

newMapping := applyIDPSyncMappingDiff(existing.Mapping, req.Add, req.Remove)
settings = idpsync.RoleSyncSettings{
Field: existing.Field,
Mapping: newMapping,
}

err = api.IDPSync.UpdateRoleSyncSettings(sysCtx, org.ID, tx, settings)
if err != nil {
return err
}

return nil
})
if err != nil {
httpapi.InternalServerError(rw, err)
return
}

aReq.New = settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.RoleSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
})
}

// @Summary Get organization IdP Sync settings
// @ID get-organization-idp-sync-settings
// @Security CoderSessionToken
Expand Down
Loading