feat: add additional patch routes for group and role idp sync (#16351)

This commit is contained in:
ケイラ
2025-01-31 12:14:24 -07:00
committed by GitHub
parent e37b7fc481
commit 0e2ae10b47
13 changed files with 1749 additions and 75 deletions
+284
View File
@@ -3438,6 +3438,100 @@ const docTemplate = `{
}
}
},
"/organizations/{organization}/settings/idpsync/groups/config": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Update group IdP Sync config",
"operationId": "update-group-idp-sync-config",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "New config values",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchGroupIDPSyncConfigRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/groups/mapping": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Update group IdP Sync mapping",
"operationId": "update-group-idp-sync-mapping",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Description of the mappings to add and remove",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchGroupIDPSyncMappingRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles": {
"get": {
"security": [
@@ -3518,6 +3612,100 @@ const docTemplate = `{
}
}
},
"/organizations/{organization}/settings/idpsync/roles/config": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Update role IdP Sync config",
"operationId": "update-role-idp-sync-config",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "New config values",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchRoleIDPSyncConfigRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles/mapping": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Update role IdP Sync mapping",
"operationId": "update-role-idp-sync-mapping",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Description of the mappings to add and remove",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchRoleIDPSyncMappingRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
}
},
"/organizations/{organization}/templates": {
"get": {
"security": [
@@ -12469,6 +12657,57 @@ const docTemplate = `{
}
}
},
"codersdk.PatchGroupIDPSyncConfigRequest": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"type": "boolean"
},
"field": {
"type": "string"
},
"regex_filter": {
"$ref": "#/definitions/regexp.Regexp"
}
}
},
"codersdk.PatchGroupIDPSyncMappingRequest": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
},
"remove": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
}
}
},
"codersdk.PatchGroupRequest": {
"type": "object",
"properties": {
@@ -12546,6 +12785,51 @@ const docTemplate = `{
}
}
},
"codersdk.PatchRoleIDPSyncConfigRequest": {
"type": "object",
"properties": {
"field": {
"type": "string"
}
}
},
"codersdk.PatchRoleIDPSyncMappingRequest": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
},
"remove": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
}
}
},
"codersdk.PatchTemplateVersionRequest": {
"type": "object",
"properties": {
+260
View File
@@ -3030,6 +3030,88 @@
}
}
},
"/organizations/{organization}/settings/idpsync/groups/config": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Update group IdP Sync config",
"operationId": "update-group-idp-sync-config",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "New config values",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchGroupIDPSyncConfigRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/groups/mapping": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Update group IdP Sync mapping",
"operationId": "update-group-idp-sync-mapping",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Description of the mappings to add and remove",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchGroupIDPSyncMappingRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles": {
"get": {
"security": [
@@ -3100,6 +3182,88 @@
}
}
},
"/organizations/{organization}/settings/idpsync/roles/config": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Update role IdP Sync config",
"operationId": "update-role-idp-sync-config",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "New config values",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchRoleIDPSyncConfigRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles/mapping": {
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Update role IdP Sync mapping",
"operationId": "update-role-idp-sync-mapping",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID or name",
"name": "organization",
"in": "path",
"required": true
},
{
"description": "Description of the mappings to add and remove",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/codersdk.PatchRoleIDPSyncMappingRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
}
},
"/organizations/{organization}/templates": {
"get": {
"security": [
@@ -11238,6 +11402,57 @@
}
}
},
"codersdk.PatchGroupIDPSyncConfigRequest": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"type": "boolean"
},
"field": {
"type": "string"
},
"regex_filter": {
"$ref": "#/definitions/regexp.Regexp"
}
}
},
"codersdk.PatchGroupIDPSyncMappingRequest": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
},
"remove": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
}
}
},
"codersdk.PatchGroupRequest": {
"type": "object",
"properties": {
@@ -11315,6 +11530,51 @@
}
}
},
"codersdk.PatchRoleIDPSyncConfigRequest": {
"type": "object",
"properties": {
"field": {
"type": "string"
}
}
},
"codersdk.PatchRoleIDPSyncMappingRequest": {
"type": "object",
"properties": {
"add": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
},
"remove": {
"type": "array",
"items": {
"type": "object",
"properties": {
"gets": {
"description": "The ID of the Coder resource the user should be added to",
"type": "string"
},
"given": {
"description": "The IdP claim the user has",
"type": "string"
}
}
}
}
}
},
"codersdk.PatchTemplateVersionRequest": {
"type": "object",
"properties": {
+1 -1
View File
@@ -30,7 +30,7 @@ func (AGPLIDPSync) GroupSyncEntitled() bool {
return false
}
func (s AGPLIDPSync) UpdateGroupSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings GroupSyncSettings) error {
func (s AGPLIDPSync) UpdateGroupSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings GroupSyncSettings) error {
orgResolver := s.Manager.OrganizationResolver(db, orgID)
err := s.SyncSettings.Group.SetRuntimeValue(ctx, orgResolver, &settings)
if err != nil {
+2 -2
View File
@@ -48,7 +48,7 @@ type IDPSync interface {
// on the settings used by IDPSync. This entry is thread safe and can be
// accessed concurrently. The settings are stored in the database.
GroupSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store) (*GroupSyncSettings, error)
UpdateGroupSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings GroupSyncSettings) error
UpdateGroupSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings GroupSyncSettings) error
// RoleSyncEntitled returns true if the deployment is entitled to role syncing.
RoleSyncEntitled() bool
@@ -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)
+1 -1
View File
@@ -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 {
+78
View File
@@ -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.
@@ -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 PatchRoleIDPSyncConfigRequest) (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 PatchRoleIDPSyncMappingRequest) (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
+256
View File
@@ -1953,6 +1953,141 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/setti
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Update group IdP Sync config
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/groups/config \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /organizations/{organization}/settings/idpsync/groups/config`
> Body parameter
```json
{
"auto_create_missing_groups": true,
"field": "string",
"regex_filter": {}
}
```
### Parameters
| Name | In | Type | Required | Description |
|----------------|------|----------------------------------------------------------------------------------------------|----------|-------------------------|
| `organization` | path | string(uuid) | true | Organization ID or name |
| `body` | body | [codersdk.PatchGroupIDPSyncConfigRequest](schemas.md#codersdkpatchgroupidpsyncconfigrequest) | true | New config values |
### Example responses
> 200 Response
```json
{
"auto_create_missing_groups": true,
"field": "string",
"legacy_group_name_mapping": {
"property1": "string",
"property2": "string"
},
"mapping": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"regex_filter": {}
}
```
### 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).
## Update group IdP Sync mapping
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/groups/mapping \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /organizations/{organization}/settings/idpsync/groups/mapping`
> Body parameter
```json
{
"add": [
{
"gets": "string",
"given": "string"
}
],
"remove": [
{
"gets": "string",
"given": "string"
}
]
}
```
### Parameters
| Name | In | Type | Required | Description |
|----------------|------|------------------------------------------------------------------------------------------------|----------|-----------------------------------------------|
| `organization` | path | string(uuid) | true | Organization ID or name |
| `body` | body | [codersdk.PatchGroupIDPSyncMappingRequest](schemas.md#codersdkpatchgroupidpsyncmappingrequest) | true | Description of the mappings to add and remove |
### Example responses
> 200 Response
```json
{
"auto_create_missing_groups": true,
"field": "string",
"legacy_group_name_mapping": {
"property1": "string",
"property2": "string"
},
"mapping": {
"property1": [
"string"
],
"property2": [
"string"
]
},
"regex_filter": {}
}
```
### 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
@@ -2061,6 +2196,127 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/setti
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Update role IdP Sync config
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/roles/config \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /organizations/{organization}/settings/idpsync/roles/config`
> Body parameter
```json
{
"field": "string"
}
```
### Parameters
| Name | In | Type | Required | Description |
|----------------|------|--------------------------------------------------------------------------------------------|----------|-------------------------|
| `organization` | path | string(uuid) | true | Organization ID or name |
| `body` | body | [codersdk.PatchRoleIDPSyncConfigRequest](schemas.md#codersdkpatchroleidpsyncconfigrequest) | true | New config values |
### 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 mapping
### Code samples
```shell
# Example request using curl
curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/roles/mapping \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Coder-Session-Token: API_KEY'
```
`PATCH /organizations/{organization}/settings/idpsync/roles/mapping`
> Body parameter
```json
{
"add": [
{
"gets": "string",
"given": "string"
}
],
"remove": [
{
"gets": "string",
"given": "string"
}
]
}
```
### Parameters
| Name | In | Type | Required | Description |
|----------------|------|----------------------------------------------------------------------------------------------|----------|-----------------------------------------------|
| `organization` | path | string(uuid) | true | Organization ID or name |
| `body` | body | [codersdk.PatchRoleIDPSyncMappingRequest](schemas.md#codersdkpatchroleidpsyncmappingrequest) | true | Description of the mappings to add and remove |
### 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).
## Fetch provisioner key details
### Code samples
+92
View File
@@ -4152,6 +4152,54 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
| » `[any property]` | array of string | false | | |
| `organization_assign_default` | boolean | false | | Organization assign default will ensure the default org is always included for every user, regardless of their claims. This preserves legacy behavior. |
## codersdk.PatchGroupIDPSyncConfigRequest
```json
{
"auto_create_missing_groups": true,
"field": "string",
"regex_filter": {}
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|------------------------------|--------------------------------|----------|--------------|-------------|
| `auto_create_missing_groups` | boolean | false | | |
| `field` | string | false | | |
| `regex_filter` | [regexp.Regexp](#regexpregexp) | false | | |
## codersdk.PatchGroupIDPSyncMappingRequest
```json
{
"add": [
{
"gets": "string",
"given": "string"
}
],
"remove": [
{
"gets": "string",
"given": "string"
}
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|-----------|-----------------|----------|--------------|----------------------------------------------------------|
| `add` | array of object | false | | |
| `» gets` | string | false | | The ID of the Coder resource the user should be added to |
| `» given` | string | false | | The IdP claim the user has |
| `remove` | array of object | false | | |
| `» gets` | string | false | | The ID of the Coder resource the user should be added to |
| `» given` | string | false | | The IdP claim the user has |
## codersdk.PatchGroupRequest
```json
@@ -4226,6 +4274,50 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
| `» gets` | string | false | | The ID of the Coder resource the user should be added to |
| `» given` | string | false | | The IdP claim the user has |
## codersdk.PatchRoleIDPSyncConfigRequest
```json
{
"field": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|---------|--------|----------|--------------|-------------|
| `field` | string | false | | |
## codersdk.PatchRoleIDPSyncMappingRequest
```json
{
"add": [
{
"gets": "string",
"given": "string"
}
],
"remove": [
{
"gets": "string",
"given": "string"
}
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|-----------|-----------------|----------|--------------|----------------------------------------------------------|
| `add` | array of object | false | | |
| `» gets` | string | false | | The ID of the Coder resource the user should be added to |
| `» given` | string | false | | The IdP claim the user has |
| `remove` | array of object | false | | |
| `» gets` | string | false | | The ID of the Coder resource the user should be added to |
| `» given` | string | false | | The IdP claim the user has |
## codersdk.PatchTemplateVersionRequest
```json
+5
View File
@@ -312,8 +312,13 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
r.Route("/organizations/{organization}/settings", func(r chi.Router) {
r.Get("/idpsync/groups", api.groupIDPSyncSettings)
r.Patch("/idpsync/groups", api.patchGroupIDPSyncSettings)
r.Patch("/idpsync/groups/config", api.patchGroupIDPSyncConfig)
r.Patch("/idpsync/groups/mapping", api.patchGroupIDPSyncMapping)
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)
+317 -31
View File
@@ -61,7 +61,6 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
ctx := r.Context()
org := httpmw.OrganizationParam(r)
auditor := *api.AGPL.Auditor.Load()
aReq, commitAudit := audit.InitRequest[idpsync.GroupSyncSettings](rw, &audit.RequestParams{
Audit: auditor,
Log: api.Logger,
@@ -104,7 +103,7 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
}
aReq.Old = *existing
err = api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, idpsync.GroupSyncSettings{
err = api.IDPSync.UpdateGroupSyncSettings(sysCtx, org.ID, api.Database, idpsync.GroupSyncSettings{
Field: req.Field,
Mapping: req.Mapping,
RegexFilter: req.RegexFilter,
@@ -132,6 +131,153 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
})
}
// @Summary Update group IdP Sync config
// @ID update-group-idp-sync-config
// @Security CoderSessionToken
// @Produce json
// @Accept json
// @Tags Enterprise
// @Success 200 {object} codersdk.GroupSyncSettings
// @Param organization path string true "Organization ID or name" format(uuid)
// @Param request body codersdk.PatchGroupIDPSyncConfigRequest true "New config values"
// @Router /organizations/{organization}/settings/idpsync/groups/config [patch]
func (api *API) patchGroupIDPSyncConfig(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
org := httpmw.OrganizationParam(r)
auditor := *api.AGPL.Auditor.Load()
aReq, commitAudit := audit.InitRequest[idpsync.GroupSyncSettings](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.PatchGroupIDPSyncConfigRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
var settings idpsync.GroupSyncSettings
//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.GroupSyncSettings(sysCtx, org.ID, tx)
if err != nil {
return err
}
aReq.Old = *existing
settings = idpsync.GroupSyncSettings{
Field: req.Field,
RegexFilter: req.RegexFilter,
AutoCreateMissing: req.AutoCreateMissing,
LegacyNameMapping: existing.LegacyNameMapping,
Mapping: existing.Mapping,
}
err = api.IDPSync.UpdateGroupSyncSettings(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.GroupSyncSettings{
Field: settings.Field,
RegexFilter: settings.RegexFilter,
AutoCreateMissing: settings.AutoCreateMissing,
LegacyNameMapping: settings.LegacyNameMapping,
Mapping: settings.Mapping,
})
}
// @Summary Update group IdP Sync mapping
// @ID update-group-idp-sync-mapping
// @Security CoderSessionToken
// @Produce json
// @Accept json
// @Tags Enterprise
// @Success 200 {object} codersdk.GroupSyncSettings
// @Param organization path string true "Organization ID or name" format(uuid)
// @Param request body codersdk.PatchGroupIDPSyncMappingRequest true "Description of the mappings to add and remove"
// @Router /organizations/{organization}/settings/idpsync/groups/mapping [patch]
func (api *API) patchGroupIDPSyncMapping(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
org := httpmw.OrganizationParam(r)
auditor := *api.AGPL.Auditor.Load()
aReq, commitAudit := audit.InitRequest[idpsync.GroupSyncSettings](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.PatchGroupIDPSyncMappingRequest
if !httpapi.Read(ctx, rw, r, &req) {
return
}
var settings idpsync.GroupSyncSettings
//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.GroupSyncSettings(sysCtx, org.ID, tx)
if err != nil {
return err
}
aReq.Old = *existing
newMapping := applyIDPSyncMappingDiff(existing.Mapping, req.Add, req.Remove)
settings = idpsync.GroupSyncSettings{
Field: existing.Field,
RegexFilter: existing.RegexFilter,
AutoCreateMissing: existing.AutoCreateMissing,
LegacyNameMapping: existing.LegacyNameMapping,
Mapping: newMapping,
}
err = api.IDPSync.UpdateGroupSyncSettings(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.GroupSyncSettings{
Field: settings.Field,
RegexFilter: settings.RegexFilter,
AutoCreateMissing: settings.AutoCreateMissing,
LegacyNameMapping: settings.LegacyNameMapping,
Mapping: settings.Mapping,
})
}
// @Summary Get role IdP Sync settings by organization
// @ID get-role-idp-sync-settings-by-organization
// @Security CoderSessionToken
@@ -203,7 +349,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,
})
@@ -225,6 +371,141 @@ 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 organization path string true "Organization ID or name" format(uuid)
// @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 organization path string true "Organization ID or name" format(uuid)
// @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
@@ -349,7 +630,7 @@ func (api *API) patchOrganizationIDPSyncConfig(rw http.ResponseWriter, r *http.R
return
}
var settings *idpsync.OrganizationSyncSettings
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 {
@@ -359,16 +640,13 @@ func (api *API) patchOrganizationIDPSyncConfig(rw http.ResponseWriter, r *http.R
}
aReq.Old = *existing
err = api.IDPSync.UpdateOrganizationSyncSettings(sysCtx, tx, idpsync.OrganizationSyncSettings{
settings = idpsync.OrganizationSyncSettings{
Field: req.Field,
AssignDefault: req.AssignDefault,
Mapping: existing.Mapping,
})
if err != nil {
return err
}
settings, err = api.IDPSync.OrganizationSyncSettings(sysCtx, tx)
err = api.IDPSync.UpdateOrganizationSyncSettings(sysCtx, tx, settings)
if err != nil {
return err
}
@@ -380,7 +658,7 @@ func (api *API) patchOrganizationIDPSyncConfig(rw http.ResponseWriter, r *http.R
return
}
aReq.New = *settings
aReq.New = settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.OrganizationSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
@@ -428,27 +706,7 @@ func (api *API) patchOrganizationIDPSyncMapping(rw http.ResponseWriter, r *http.
}
aReq.Old = *existing
newMapping := make(map[string][]uuid.UUID)
// Copy existing mapping
for key, ids := range existing.Mapping {
newMapping[key] = append(newMapping[key], ids...)
}
// Add unique entries
for _, mapping := range req.Add {
if !slice.Contains(newMapping[mapping.Given], mapping.Gets) {
newMapping[mapping.Given] = append(newMapping[mapping.Given], mapping.Gets)
}
}
// Remove entries
for _, mapping := range req.Remove {
newMapping[mapping.Given] = slices.DeleteFunc(newMapping[mapping.Given], func(u uuid.UUID) bool {
return u == mapping.Gets
})
}
newMapping := applyIDPSyncMappingDiff(existing.Mapping, req.Add, req.Remove)
settings = idpsync.OrganizationSyncSettings{
Field: existing.Field,
Mapping: newMapping,
@@ -581,3 +839,31 @@ func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, rw http.ResponseWriter,
httpapi.Write(ctx, rw, http.StatusOK, fieldValues)
}
func applyIDPSyncMappingDiff[IDType uuid.UUID | string](
previous map[string][]IDType,
add, remove []codersdk.IDPSyncMapping[IDType],
) map[string][]IDType {
next := make(map[string][]IDType)
// Copy existing mapping
for key, ids := range previous {
next[key] = append(next[key], ids...)
}
// Add unique entries
for _, mapping := range add {
if !slice.Contains(next[mapping.Given], mapping.Gets) {
next[mapping.Given] = append(next[mapping.Given], mapping.Gets)
}
}
// Remove entries
for _, mapping := range remove {
next[mapping.Given] = slices.DeleteFunc(next[mapping.Given], func(u IDType) bool {
return u == mapping.Gets
})
}
return next
}
+117
View File
@@ -0,0 +1,117 @@
package coderd
import (
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/codersdk"
)
func TestApplyIDPSyncMappingDiff(t *testing.T) {
t.Parallel()
t.Run("with UUIDs", func(t *testing.T) {
t.Parallel()
id := []uuid.UUID{
uuid.MustParse("00000000-b8bd-46bb-bb6c-6c2b2c0dd2ea"),
uuid.MustParse("01000000-fbe8-464c-9429-fe01a03f3644"),
uuid.MustParse("02000000-0926-407b-9998-39af62e3d0c5"),
uuid.MustParse("03000000-92f6-4bfd-bba6-0f54667b131c"),
}
mapping := applyIDPSyncMappingDiff(map[string][]uuid.UUID{},
[]codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: id[0]},
{Given: "wibble", Gets: id[1]},
{Given: "wobble", Gets: id[0]},
{Given: "wobble", Gets: id[1]},
{Given: "wobble", Gets: id[2]},
{Given: "wobble", Gets: id[3]},
{Given: "wooble", Gets: id[0]},
},
// Remove takes priority over Add, so `3` should not actually be added.
[]codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wobble", Gets: id[3]},
},
)
expected := map[string][]uuid.UUID{
"wibble": {id[0], id[1]},
"wobble": {id[0], id[1], id[2]},
"wooble": {id[0]},
}
require.Equal(t, expected, mapping)
mapping = applyIDPSyncMappingDiff(mapping,
[]codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: id[2]},
{Given: "wobble", Gets: id[3]},
{Given: "wooble", Gets: id[0]},
},
[]codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: id[0]},
{Given: "wobble", Gets: id[1]},
},
)
expected = map[string][]uuid.UUID{
"wibble": {id[1], id[2]},
"wobble": {id[0], id[2], id[3]},
"wooble": {id[0]},
}
require.Equal(t, expected, mapping)
})
t.Run("with strings", func(t *testing.T) {
t.Parallel()
mapping := applyIDPSyncMappingDiff(map[string][]string{},
[]codersdk.IDPSyncMapping[string]{
{Given: "wibble", Gets: "group-00"},
{Given: "wibble", Gets: "group-01"},
{Given: "wobble", Gets: "group-00"},
{Given: "wobble", Gets: "group-01"},
{Given: "wobble", Gets: "group-02"},
{Given: "wobble", Gets: "group-03"},
{Given: "wooble", Gets: "group-00"},
},
// Remove takes priority over Add, so `3` should not actually be added.
[]codersdk.IDPSyncMapping[string]{
{Given: "wobble", Gets: "group-03"},
},
)
expected := map[string][]string{
"wibble": {"group-00", "group-01"},
"wobble": {"group-00", "group-01", "group-02"},
"wooble": {"group-00"},
}
require.Equal(t, expected, mapping)
mapping = applyIDPSyncMappingDiff(mapping,
[]codersdk.IDPSyncMapping[string]{
{Given: "wibble", Gets: "group-02"},
{Given: "wobble", Gets: "group-03"},
{Given: "wooble", Gets: "group-00"},
},
[]codersdk.IDPSyncMapping[string]{
{Given: "wibble", Gets: "group-00"},
{Given: "wobble", Gets: "group-01"},
},
)
expected = map[string][]string{
"wibble": {"group-01", "group-02"},
"wobble": {"group-00", "group-02", "group-03"},
"wooble": {"group-00"},
}
require.Equal(t, expected, mapping)
})
}
+312 -40
View File
@@ -141,6 +141,171 @@ func TestPatchGroupSyncSettings(t *testing.T) {
})
}
func TestPatchGroupSyncConfig(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,
},
},
})
orgID := user.OrganizationID
orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, orgID, rbac.ScopedRoleOrgAdmin(user.OrganizationID))
mapping := map[string][]uuid.UUID{"wibble": {uuid.New()}}
ctx := testutil.Context(t, testutil.WaitShort)
_, err := orgAdmin.PatchGroupIDPSyncSettings(ctx, orgID.String(), codersdk.GroupSyncSettings{
Field: "wibble",
RegexFilter: regexp.MustCompile("wib{2,}le"),
AutoCreateMissing: false,
Mapping: mapping,
})
require.NoError(t, err)
fetchedSettings, err := orgAdmin.GroupIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wibble", fetchedSettings.Field)
require.Equal(t, "wib{2,}le", fetchedSettings.RegexFilter.String())
require.Equal(t, false, fetchedSettings.AutoCreateMissing)
require.Equal(t, mapping, fetchedSettings.Mapping)
ctx = testutil.Context(t, testutil.WaitShort)
settings, err := orgAdmin.PatchGroupIDPSyncConfig(ctx, orgID.String(), codersdk.PatchGroupIDPSyncConfigRequest{
Field: "wobble",
RegexFilter: regexp.MustCompile("wob{2,}le"),
AutoCreateMissing: true,
})
require.NoError(t, err)
require.Equal(t, "wobble", settings.Field)
require.Equal(t, "wob{2,}le", settings.RegexFilter.String())
require.Equal(t, true, settings.AutoCreateMissing)
require.Equal(t, mapping, settings.Mapping)
fetchedSettings, err = orgAdmin.GroupIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wobble", fetchedSettings.Field)
require.Equal(t, "wob{2,}le", fetchedSettings.RegexFilter.String())
require.Equal(t, true, fetchedSettings.AutoCreateMissing)
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.PatchGroupIDPSyncConfig(ctx, user.OrganizationID.String(), codersdk.PatchGroupIDPSyncConfigRequest{})
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
})
}
func TestPatchGroupSyncMapping(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,
},
},
})
orgID := user.OrganizationID
orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, orgID, rbac.ScopedRoleOrgAdmin(user.OrganizationID))
// These IDs are easier to visually diff if the test fails than truly random
// ones.
orgs := []uuid.UUID{
uuid.MustParse("00000000-b8bd-46bb-bb6c-6c2b2c0dd2ea"),
uuid.MustParse("01000000-fbe8-464c-9429-fe01a03f3644"),
uuid.MustParse("02000000-0926-407b-9998-39af62e3d0c5"),
}
ctx := testutil.Context(t, testutil.WaitShort)
_, err := orgAdmin.PatchGroupIDPSyncSettings(ctx, orgID.String(), codersdk.GroupSyncSettings{
Field: "wibble",
RegexFilter: regexp.MustCompile("wib{2,}le"),
AutoCreateMissing: true,
Mapping: map[string][]uuid.UUID{"wobble": {orgs[0]}},
})
require.NoError(t, err)
ctx = testutil.Context(t, testutil.WaitShort)
settings, err := orgAdmin.PatchGroupIDPSyncMapping(ctx, orgID.String(), codersdk.PatchGroupIDPSyncMappingRequest{
Add: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: orgs[0]},
{Given: "wobble", Gets: orgs[1]},
{Given: "wobble", Gets: orgs[2]},
},
// Remove takes priority over Add, so "3" should not actually be added to wooble.
Remove: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wobble", Gets: orgs[1]},
},
})
expected := map[string][]uuid.UUID{
"wibble": {orgs[0]},
"wobble": {orgs[0], orgs[2]},
}
require.NoError(t, err)
require.Equal(t, expected, settings.Mapping)
fetchedSettings, err := orgAdmin.GroupIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wibble", fetchedSettings.Field)
require.Equal(t, "wib{2,}le", fetchedSettings.RegexFilter.String())
require.Equal(t, true, fetchedSettings.AutoCreateMissing)
require.Equal(t, expected, 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.PatchGroupIDPSyncMapping(ctx, user.OrganizationID.String(), codersdk.PatchGroupIDPSyncMappingRequest{})
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
})
}
func TestGetRoleSyncSettings(t *testing.T) {
t.Parallel()
@@ -233,6 +398,150 @@ func TestPatchRoleSyncSettings(t *testing.T) {
})
}
func TestPatchRoleSyncConfig(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,
},
},
})
orgID := user.OrganizationID
orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, orgID, rbac.ScopedRoleOrgAdmin(user.OrganizationID))
mapping := map[string][]string{"wibble": {"group-01"}}
ctx := testutil.Context(t, testutil.WaitShort)
_, err := orgAdmin.PatchRoleIDPSyncSettings(ctx, orgID.String(), codersdk.RoleSyncSettings{
Field: "wibble",
Mapping: mapping,
})
require.NoError(t, err)
fetchedSettings, err := orgAdmin.RoleIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wibble", fetchedSettings.Field)
require.Equal(t, mapping, fetchedSettings.Mapping)
ctx = testutil.Context(t, testutil.WaitShort)
settings, err := orgAdmin.PatchRoleIDPSyncConfig(ctx, orgID.String(), codersdk.PatchRoleIDPSyncConfigRequest{
Field: "wobble",
})
require.NoError(t, err)
require.Equal(t, "wobble", settings.Field)
require.Equal(t, mapping, settings.Mapping)
fetchedSettings, err = orgAdmin.RoleIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wobble", fetchedSettings.Field)
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.PatchGroupIDPSyncConfig(ctx, user.OrganizationID.String(), codersdk.PatchGroupIDPSyncConfigRequest{})
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
})
}
func TestPatchRoleSyncMapping(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,
},
},
})
orgID := user.OrganizationID
orgAdmin, _ := coderdtest.CreateAnotherUser(t, owner, orgID, rbac.ScopedRoleOrgAdmin(user.OrganizationID))
ctx := testutil.Context(t, testutil.WaitShort)
_, err := orgAdmin.PatchRoleIDPSyncSettings(ctx, orgID.String(), codersdk.RoleSyncSettings{
Field: "wibble",
Mapping: map[string][]string{"wobble": {"group-00"}},
})
require.NoError(t, err)
ctx = testutil.Context(t, testutil.WaitShort)
settings, err := orgAdmin.PatchRoleIDPSyncMapping(ctx, orgID.String(), codersdk.PatchRoleIDPSyncMappingRequest{
Add: []codersdk.IDPSyncMapping[string]{
{Given: "wibble", Gets: "group-00"},
{Given: "wobble", Gets: "group-01"},
{Given: "wobble", Gets: "group-02"},
},
// Remove takes priority over Add, so "3" should not actually be added to wooble.
Remove: []codersdk.IDPSyncMapping[string]{
{Given: "wobble", Gets: "group-01"},
},
})
expected := map[string][]string{
"wibble": {"group-00"},
"wobble": {"group-00", "group-02"},
}
require.NoError(t, err)
require.Equal(t, expected, settings.Mapping)
fetchedSettings, err := orgAdmin.RoleIDPSyncSettings(ctx, orgID.String())
require.NoError(t, err)
require.Equal(t, "wibble", fetchedSettings.Field)
require.Equal(t, expected, 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.PatchGroupIDPSyncMapping(ctx, user.OrganizationID.String(), codersdk.PatchGroupIDPSyncMappingRequest{})
var apiError *codersdk.Error
require.ErrorAs(t, err, &apiError)
require.Equal(t, http.StatusForbidden, apiError.StatusCode())
})
}
func TestGetOrganizationSyncSettings(t *testing.T) {
t.Parallel()
@@ -416,11 +725,6 @@ func TestPatchOrganizationSyncMapping(t *testing.T) {
uuid.MustParse("00000000-b8bd-46bb-bb6c-6c2b2c0dd2ea"),
uuid.MustParse("01000000-fbe8-464c-9429-fe01a03f3644"),
uuid.MustParse("02000000-0926-407b-9998-39af62e3d0c5"),
uuid.MustParse("03000000-92f6-4bfd-bba6-0f54667b131c"),
uuid.MustParse("04000000-b9d0-46fe-910f-6e2ea0c62caa"),
uuid.MustParse("05000000-67c0-4c19-a52d-0dc3f65abee0"),
uuid.MustParse("06000000-a8a8-4a2c-bdd0-b59aa6882b55"),
uuid.MustParse("07000000-5390-4cc7-a9c8-e4330a683ae7"),
}
ctx := testutil.Context(t, testutil.WaitShort)
@@ -428,23 +732,18 @@ func TestPatchOrganizationSyncMapping(t *testing.T) {
settings, err := owner.PatchOrganizationIDPSyncMapping(ctx, codersdk.PatchOrganizationIDPSyncMappingRequest{
Add: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: orgs[0]},
{Given: "wibble", Gets: orgs[1]},
{Given: "wobble", Gets: orgs[0]},
{Given: "wobble", Gets: orgs[1]},
{Given: "wobble", Gets: orgs[2]},
{Given: "wobble", Gets: orgs[3]},
{Given: "wooble", Gets: orgs[0]},
},
// Remove takes priority over Add, so "3" should not actually be added to wooble.
Remove: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wobble", Gets: orgs[3]},
{Given: "wobble", Gets: orgs[1]},
},
})
expected := map[string][]uuid.UUID{
"wibble": {orgs[0], orgs[1]},
"wobble": {orgs[0], orgs[1], orgs[2]},
"wooble": {orgs[0]},
"wibble": {orgs[0]},
"wobble": {orgs[0], orgs[2]},
}
require.NoError(t, err)
@@ -453,33 +752,6 @@ func TestPatchOrganizationSyncMapping(t *testing.T) {
fetchedSettings, err := owner.OrganizationIDPSyncSettings(ctx)
require.NoError(t, err)
require.Equal(t, expected, fetchedSettings.Mapping)
ctx = testutil.Context(t, testutil.WaitShort)
settings, err = owner.PatchOrganizationIDPSyncMapping(ctx, codersdk.PatchOrganizationIDPSyncMappingRequest{
Add: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: orgs[2]},
{Given: "wobble", Gets: orgs[3]},
{Given: "wooble", Gets: orgs[0]},
},
// Remove takes priority over Add, so `f` should not actually be added.
Remove: []codersdk.IDPSyncMapping[uuid.UUID]{
{Given: "wibble", Gets: orgs[0]},
{Given: "wobble", Gets: orgs[1]},
},
})
expected = map[string][]uuid.UUID{
"wibble": {orgs[1], orgs[2]},
"wobble": {orgs[0], orgs[2], orgs[3]},
"wooble": {orgs[0]},
}
require.NoError(t, err)
require.Equal(t, expected, settings.Mapping)
fetchedSettings, err = owner.OrganizationIDPSyncSettings(ctx)
require.NoError(t, err)
require.Equal(t, expected, fetchedSettings.Mapping)
})
t.Run("NotAuthorized", func(t *testing.T) {
+24
View File
@@ -1455,6 +1455,19 @@ export interface Pagination {
readonly offset?: number;
}
// From codersdk/idpsync.go
export interface PatchGroupIDPSyncConfigRequest {
readonly field: string;
readonly regex_filter: string | null;
readonly auto_create_missing_groups: boolean;
}
// From codersdk/idpsync.go
export interface PatchGroupIDPSyncMappingRequest {
readonly Add: readonly IDPSyncMapping<string>[];
readonly Remove: readonly IDPSyncMapping<string>[];
}
// From codersdk/groups.go
export interface PatchGroupRequest {
readonly add_users: readonly string[];
@@ -1477,6 +1490,17 @@ export interface PatchOrganizationIDPSyncMappingRequest {
readonly Remove: readonly IDPSyncMapping<string>[];
}
// From codersdk/idpsync.go
export interface PatchRoleIDPSyncConfigRequest {
readonly field: string;
}
// From codersdk/idpsync.go
export interface PatchRoleIDPSyncMappingRequest {
readonly Add: readonly IDPSyncMapping<string>[];
readonly Remove: readonly IDPSyncMapping<string>[];
}
// From codersdk/templateversions.go
export interface PatchTemplateVersionRequest {
readonly name: string;