mirror of
https://github.com/coder/coder.git
synced 2025-03-15 19:19:58 +00:00
feat: add endpoint for partial updates to org sync field and assign_default (#16337)
This commit is contained in:
50
coderd/apidoc/docs.go
generated
50
coderd/apidoc/docs.go
generated
@ -4248,6 +4248,45 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/idpsync/organization/config": {
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Enterprise"
|
||||
],
|
||||
"summary": "Update organization IdP Sync config",
|
||||
"operationId": "update-organization-idp-sync-config",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "New config values",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PatchOrganizationIDPSyncConfigRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationSyncSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/idpsync/organization/mapping": {
|
||||
"patch": {
|
||||
"security": [
|
||||
@ -12459,6 +12498,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchOrganizationIDPSyncConfigRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assign_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchOrganizationIDPSyncMappingRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
44
coderd/apidoc/swagger.json
generated
44
coderd/apidoc/swagger.json
generated
@ -3744,6 +3744,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/idpsync/organization/config": {
|
||||
"patch": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Enterprise"],
|
||||
"summary": "Update organization IdP Sync config",
|
||||
"operationId": "update-organization-idp-sync-config",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "New config values",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.PatchOrganizationIDPSyncConfigRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.OrganizationSyncSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/settings/idpsync/organization/mapping": {
|
||||
"patch": {
|
||||
"security": [
|
||||
@ -11234,6 +11267,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchOrganizationIDPSyncConfigRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assign_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"field": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.PatchOrganizationIDPSyncMappingRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -144,6 +144,25 @@ func (c *Client) PatchOrganizationIDPSyncSettings(ctx context.Context, req Organ
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
type PatchOrganizationIDPSyncConfigRequest struct {
|
||||
Field string `json:"field"`
|
||||
AssignDefault bool `json:"assign_default"`
|
||||
}
|
||||
|
||||
func (c *Client) PatchOrganizationIDPSyncConfig(ctx context.Context, req PatchOrganizationIDPSyncConfigRequest) (OrganizationSyncSettings, error) {
|
||||
res, err := c.Request(ctx, http.MethodPatch, "/api/v2/settings/idpsync/organization/config", req)
|
||||
if err != nil {
|
||||
return OrganizationSyncSettings{}, xerrors.Errorf("make request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return OrganizationSyncSettings{}, ReadBodyAsError(res)
|
||||
}
|
||||
var resp OrganizationSyncSettings
|
||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||
}
|
||||
|
||||
// If the same mapping is present in both Add and Remove, Remove will take presidence.
|
||||
type PatchOrganizationIDPSyncMappingRequest struct {
|
||||
Add []IDPSyncMapping[uuid.UUID]
|
||||
|
56
docs/reference/api/enterprise.md
generated
56
docs/reference/api/enterprise.md
generated
@ -2677,6 +2677,62 @@ curl -X PATCH http://coder-server:8080/api/v2/settings/idpsync/organization \
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Update organization IdP Sync config
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X PATCH http://coder-server:8080/api/v2/settings/idpsync/organization/config \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`PATCH /settings/idpsync/organization/config`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"assign_default": true,
|
||||
"field": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|--------|------|------------------------------------------------------------------------------------------------------------|----------|-------------------|
|
||||
| `body` | body | [codersdk.PatchOrganizationIDPSyncConfigRequest](schemas.md#codersdkpatchorganizationidpsyncconfigrequest) | true | New config values |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"field": "string",
|
||||
"mapping": {
|
||||
"property1": [
|
||||
"string"
|
||||
],
|
||||
"property2": [
|
||||
"string"
|
||||
]
|
||||
},
|
||||
"organization_assign_default": true
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|----------------------------------------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.OrganizationSyncSettings](schemas.md#codersdkorganizationsyncsettings) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Update organization IdP Sync mapping
|
||||
|
||||
### Code samples
|
||||
|
16
docs/reference/api/schemas.md
generated
16
docs/reference/api/schemas.md
generated
@ -4180,6 +4180,22 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
| `quota_allowance` | integer | false | | |
|
||||
| `remove_users` | array of string | false | | |
|
||||
|
||||
## codersdk.PatchOrganizationIDPSyncConfigRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"assign_default": true,
|
||||
"field": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------------|---------|----------|--------------|-------------|
|
||||
| `assign_default` | boolean | false | | |
|
||||
| `field` | string | false | | |
|
||||
|
||||
## codersdk.PatchOrganizationIDPSyncMappingRequest
|
||||
|
||||
```json
|
||||
|
@ -295,6 +295,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Route("/organization", func(r chi.Router) {
|
||||
r.Get("/", api.organizationIDPSyncSettings)
|
||||
r.Patch("/", api.patchOrganizationIDPSyncSettings)
|
||||
r.Patch("/config", api.patchOrganizationIDPSyncConfig)
|
||||
r.Patch("/mapping", api.patchOrganizationIDPSyncMapping)
|
||||
})
|
||||
|
||||
|
@ -319,6 +319,75 @@ func (api *API) patchOrganizationIDPSyncSettings(rw http.ResponseWriter, r *http
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Update organization IdP Sync config
|
||||
// @ID update-organization-idp-sync-config
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Accept json
|
||||
// @Tags Enterprise
|
||||
// @Success 200 {object} codersdk.OrganizationSyncSettings
|
||||
// @Param request body codersdk.PatchOrganizationIDPSyncConfigRequest true "New config values"
|
||||
// @Router /settings/idpsync/organization/config [patch]
|
||||
func (api *API) patchOrganizationIDPSyncConfig(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
auditor := *api.AGPL.Auditor.Load()
|
||||
aReq, commitAudit := audit.InitRequest[idpsync.OrganizationSyncSettings](rw, &audit.RequestParams{
|
||||
Audit: auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionWrite,
|
||||
})
|
||||
defer commitAudit()
|
||||
|
||||
if !api.Authorize(r, policy.ActionUpdate, rbac.ResourceIdpsyncSettings) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
var req codersdk.PatchOrganizationIDPSyncConfigRequest
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
var settings *idpsync.OrganizationSyncSettings
|
||||
//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.OrganizationSyncSettings(sysCtx, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aReq.Old = *existing
|
||||
|
||||
err = api.IDPSync.UpdateOrganizationSyncSettings(sysCtx, tx, idpsync.OrganizationSyncSettings{
|
||||
Field: req.Field,
|
||||
AssignDefault: req.AssignDefault,
|
||||
Mapping: existing.Mapping,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings, err = api.IDPSync.OrganizationSyncSettings(sysCtx, tx)
|
||||
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.OrganizationSyncSettings{
|
||||
Field: settings.Field,
|
||||
Mapping: settings.Mapping,
|
||||
AssignDefault: settings.AssignDefault,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Update organization IdP Sync mapping
|
||||
// @ID update-organization-idp-sync-mapping
|
||||
// @Security CoderSessionToken
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func TestGetGroupSyncConfig(t *testing.T) {
|
||||
func TestGetGroupSyncSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
@ -83,7 +83,7 @@ func TestGetGroupSyncConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchGroupSyncConfig(t *testing.T) {
|
||||
func TestPatchGroupSyncSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
@ -141,7 +141,7 @@ func TestPatchGroupSyncConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRoleSyncConfig(t *testing.T) {
|
||||
func TestGetRoleSyncSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
@ -175,7 +175,7 @@ func TestGetRoleSyncConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchRoleSyncConfig(t *testing.T) {
|
||||
func TestPatchRoleSyncSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
@ -323,6 +323,78 @@ func TestPatchOrganizationSyncSettings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchOrganizationSyncConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureCustomRoles: 1,
|
||||
codersdk.FeatureMultipleOrganizations: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
mapping := map[string][]uuid.UUID{"wibble": {user.OrganizationID}}
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
//nolint:gocritic // Only owners can change Organization IdP sync settings
|
||||
_, err := owner.PatchOrganizationIDPSyncSettings(ctx, codersdk.OrganizationSyncSettings{
|
||||
Field: "wibble",
|
||||
AssignDefault: true,
|
||||
Mapping: mapping,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
fetchedSettings, err := owner.OrganizationIDPSyncSettings(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "wibble", fetchedSettings.Field)
|
||||
require.Equal(t, true, fetchedSettings.AssignDefault)
|
||||
require.Equal(t, mapping, fetchedSettings.Mapping)
|
||||
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
settings, err := owner.PatchOrganizationIDPSyncConfig(ctx, codersdk.PatchOrganizationIDPSyncConfigRequest{
|
||||
Field: "wobble",
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "wobble", settings.Field)
|
||||
require.Equal(t, false, settings.AssignDefault)
|
||||
require.Equal(t, mapping, settings.Mapping)
|
||||
|
||||
fetchedSettings, err = owner.OrganizationIDPSyncSettings(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "wobble", fetchedSettings.Field)
|
||||
require.Equal(t, false, fetchedSettings.AssignDefault)
|
||||
require.Equal(t, mapping, fetchedSettings.Mapping)
|
||||
})
|
||||
|
||||
t.Run("NotAuthorized", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
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.PatchOrganizationIDPSyncConfig(ctx, codersdk.PatchOrganizationIDPSyncConfigRequest{})
|
||||
var apiError *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiError)
|
||||
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPatchOrganizationSyncMapping(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
6
site/src/api/typesGenerated.ts
generated
6
site/src/api/typesGenerated.ts
generated
@ -1465,6 +1465,12 @@ export interface PatchGroupRequest {
|
||||
readonly quota_allowance: number | null;
|
||||
}
|
||||
|
||||
// From codersdk/idpsync.go
|
||||
export interface PatchOrganizationIDPSyncConfigRequest {
|
||||
readonly field: string;
|
||||
readonly assign_default: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/idpsync.go
|
||||
export interface PatchOrganizationIDPSyncMappingRequest {
|
||||
readonly Add: readonly IDPSyncMapping<string>[];
|
||||
|
Reference in New Issue
Block a user