diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 791b3c6f145e8..5780443a42de1 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -12937,7 +12937,12 @@ const docTemplate = `{
"organization",
"oauth2_provider_app",
"oauth2_provider_app_secret",
- "custom_role"
+ "custom_role",
+ "organization_member",
+ "notification_template",
+ "idp_sync_settings_organization",
+ "idp_sync_settings_group",
+ "idp_sync_settings_role"
],
"x-enum-varnames": [
"ResourceTypeTemplate",
@@ -12956,7 +12961,12 @@ const docTemplate = `{
"ResourceTypeOrganization",
"ResourceTypeOAuth2ProviderApp",
"ResourceTypeOAuth2ProviderAppSecret",
- "ResourceTypeCustomRole"
+ "ResourceTypeCustomRole",
+ "ResourceTypeOrganizationMember",
+ "ResourceTypeNotificationTemplate",
+ "ResourceTypeIdpSyncSettingsOrganization",
+ "ResourceTypeIdpSyncSettingsGroup",
+ "ResourceTypeIdpSyncSettingsRole"
]
},
"codersdk.Response": {
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index abd329103579e..1ecb6d185e03c 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -11694,7 +11694,12 @@
"organization",
"oauth2_provider_app",
"oauth2_provider_app_secret",
- "custom_role"
+ "custom_role",
+ "organization_member",
+ "notification_template",
+ "idp_sync_settings_organization",
+ "idp_sync_settings_group",
+ "idp_sync_settings_role"
],
"x-enum-varnames": [
"ResourceTypeTemplate",
@@ -11713,7 +11718,12 @@
"ResourceTypeOrganization",
"ResourceTypeOAuth2ProviderApp",
"ResourceTypeOAuth2ProviderAppSecret",
- "ResourceTypeCustomRole"
+ "ResourceTypeCustomRole",
+ "ResourceTypeOrganizationMember",
+ "ResourceTypeNotificationTemplate",
+ "ResourceTypeIdpSyncSettingsOrganization",
+ "ResourceTypeIdpSyncSettingsGroup",
+ "ResourceTypeIdpSyncSettingsRole"
]
},
"codersdk.Response": {
diff --git a/coderd/audit/diff.go b/coderd/audit/diff.go
index 8d5923d575054..98e47e91893cb 100644
--- a/coderd/audit/diff.go
+++ b/coderd/audit/diff.go
@@ -2,6 +2,7 @@ package audit
import (
"github.com/coder/coder/v2/coderd/database"
+ "github.com/coder/coder/v2/coderd/idpsync"
)
// Auditable is mostly a marker interface. It contains a definitive list of all
@@ -26,7 +27,10 @@ type Auditable interface {
database.CustomRole |
database.AuditableOrganizationMember |
database.Organization |
- database.NotificationTemplate
+ database.NotificationTemplate |
+ idpsync.OrganizationSyncSettings |
+ idpsync.GroupSyncSettings |
+ idpsync.RoleSyncSettings
}
// Map is a map of changed fields in an audited resource. It maps field names to
diff --git a/coderd/audit/request.go b/coderd/audit/request.go
index c8b7bf17b4b96..05c18e32fd183 100644
--- a/coderd/audit/request.go
+++ b/coderd/audit/request.go
@@ -20,6 +20,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/httpmw"
+ "github.com/coder/coder/v2/coderd/idpsync"
"github.com/coder/coder/v2/coderd/tracing"
)
@@ -121,11 +122,22 @@ func ResourceTarget[T Auditable](tgt T) string {
return typed.Name
case database.NotificationTemplate:
return typed.Name
+ case idpsync.OrganizationSyncSettings:
+ return "Organization Sync"
+ case idpsync.GroupSyncSettings:
+ return "Organization Group Sync"
+ case idpsync.RoleSyncSettings:
+ return "Organization Role Sync"
default:
panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt))
}
}
+// noID can be used for resources that do not have an uuid.
+// An example is singleton configuration resources.
+// 51A51C = "Static"
+var noID = uuid.MustParse("51A51C00-0000-0000-0000-000000000000")
+
func ResourceID[T Auditable](tgt T) uuid.UUID {
switch typed := any(tgt).(type) {
case database.Template:
@@ -169,6 +181,12 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
return typed.ID
case database.NotificationTemplate:
return typed.ID
+ case idpsync.OrganizationSyncSettings:
+ return noID // Deployment all uses the same org sync settings
+ case idpsync.GroupSyncSettings:
+ return noID // Org field on audit log has org id
+ case idpsync.RoleSyncSettings:
+ return noID // Org field on audit log has org id
default:
panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt))
}
@@ -214,6 +232,12 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
return database.ResourceTypeOrganization
case database.NotificationTemplate:
return database.ResourceTypeNotificationTemplate
+ case idpsync.OrganizationSyncSettings:
+ return database.ResourceTypeIdpSyncSettingsOrganization
+ case idpsync.RoleSyncSettings:
+ return database.ResourceTypeIdpSyncSettingsRole
+ case idpsync.GroupSyncSettings:
+ return database.ResourceTypeIdpSyncSettingsGroup
default:
panic(fmt.Sprintf("unknown resource %T for ResourceType", typed))
}
@@ -261,6 +285,12 @@ func ResourceRequiresOrgID[T Auditable]() bool {
return true
case database.NotificationTemplate:
return false
+ case idpsync.OrganizationSyncSettings:
+ return false
+ case idpsync.GroupSyncSettings:
+ return true
+ case idpsync.RoleSyncSettings:
+ return true
default:
panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt))
}
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 782bc4969d799..f91a5371f06f6 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -190,7 +190,10 @@ CREATE TYPE resource_type AS ENUM (
'custom_role',
'organization_member',
'notifications_settings',
- 'notification_template'
+ 'notification_template',
+ 'idp_sync_settings_organization',
+ 'idp_sync_settings_group',
+ 'idp_sync_settings_role'
);
CREATE TYPE startup_script_behavior AS ENUM (
diff --git a/coderd/database/migrations/000281_idpsync_settings.down.sql b/coderd/database/migrations/000281_idpsync_settings.down.sql
new file mode 100644
index 0000000000000..362f597df0911
--- /dev/null
+++ b/coderd/database/migrations/000281_idpsync_settings.down.sql
@@ -0,0 +1 @@
+-- Nothing to do
diff --git a/coderd/database/migrations/000281_idpsync_settings.up.sql b/coderd/database/migrations/000281_idpsync_settings.up.sql
new file mode 100644
index 0000000000000..4b5983ee71576
--- /dev/null
+++ b/coderd/database/migrations/000281_idpsync_settings.up.sql
@@ -0,0 +1,4 @@
+-- Allow modifications to notification templates to be audited.
+ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'idp_sync_settings_organization';
+ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'idp_sync_settings_group';
+ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'idp_sync_settings_role';
diff --git a/coderd/database/models.go b/coderd/database/models.go
index e5ddebcbc8b9a..e9a5f93051ba5 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -1524,25 +1524,28 @@ func AllProvisionerTypeValues() []ProvisionerType {
type ResourceType string
const (
- ResourceTypeOrganization ResourceType = "organization"
- ResourceTypeTemplate ResourceType = "template"
- ResourceTypeTemplateVersion ResourceType = "template_version"
- ResourceTypeUser ResourceType = "user"
- ResourceTypeWorkspace ResourceType = "workspace"
- ResourceTypeGitSshKey ResourceType = "git_ssh_key"
- ResourceTypeApiKey ResourceType = "api_key"
- ResourceTypeGroup ResourceType = "group"
- ResourceTypeWorkspaceBuild ResourceType = "workspace_build"
- ResourceTypeLicense ResourceType = "license"
- ResourceTypeWorkspaceProxy ResourceType = "workspace_proxy"
- ResourceTypeConvertLogin ResourceType = "convert_login"
- ResourceTypeHealthSettings ResourceType = "health_settings"
- ResourceTypeOauth2ProviderApp ResourceType = "oauth2_provider_app"
- ResourceTypeOauth2ProviderAppSecret ResourceType = "oauth2_provider_app_secret"
- ResourceTypeCustomRole ResourceType = "custom_role"
- ResourceTypeOrganizationMember ResourceType = "organization_member"
- ResourceTypeNotificationsSettings ResourceType = "notifications_settings"
- ResourceTypeNotificationTemplate ResourceType = "notification_template"
+ ResourceTypeOrganization ResourceType = "organization"
+ ResourceTypeTemplate ResourceType = "template"
+ ResourceTypeTemplateVersion ResourceType = "template_version"
+ ResourceTypeUser ResourceType = "user"
+ ResourceTypeWorkspace ResourceType = "workspace"
+ ResourceTypeGitSshKey ResourceType = "git_ssh_key"
+ ResourceTypeApiKey ResourceType = "api_key"
+ ResourceTypeGroup ResourceType = "group"
+ ResourceTypeWorkspaceBuild ResourceType = "workspace_build"
+ ResourceTypeLicense ResourceType = "license"
+ ResourceTypeWorkspaceProxy ResourceType = "workspace_proxy"
+ ResourceTypeConvertLogin ResourceType = "convert_login"
+ ResourceTypeHealthSettings ResourceType = "health_settings"
+ ResourceTypeOauth2ProviderApp ResourceType = "oauth2_provider_app"
+ ResourceTypeOauth2ProviderAppSecret ResourceType = "oauth2_provider_app_secret"
+ ResourceTypeCustomRole ResourceType = "custom_role"
+ ResourceTypeOrganizationMember ResourceType = "organization_member"
+ ResourceTypeNotificationsSettings ResourceType = "notifications_settings"
+ ResourceTypeNotificationTemplate ResourceType = "notification_template"
+ ResourceTypeIdpSyncSettingsOrganization ResourceType = "idp_sync_settings_organization"
+ ResourceTypeIdpSyncSettingsGroup ResourceType = "idp_sync_settings_group"
+ ResourceTypeIdpSyncSettingsRole ResourceType = "idp_sync_settings_role"
)
func (e *ResourceType) Scan(src interface{}) error {
@@ -1600,7 +1603,10 @@ func (e ResourceType) Valid() bool {
ResourceTypeCustomRole,
ResourceTypeOrganizationMember,
ResourceTypeNotificationsSettings,
- ResourceTypeNotificationTemplate:
+ ResourceTypeNotificationTemplate,
+ ResourceTypeIdpSyncSettingsOrganization,
+ ResourceTypeIdpSyncSettingsGroup,
+ ResourceTypeIdpSyncSettingsRole:
return true
}
return false
@@ -1627,6 +1633,9 @@ func AllResourceTypeValues() []ResourceType {
ResourceTypeOrganizationMember,
ResourceTypeNotificationsSettings,
ResourceTypeNotificationTemplate,
+ ResourceTypeIdpSyncSettingsOrganization,
+ ResourceTypeIdpSyncSettingsGroup,
+ ResourceTypeIdpSyncSettingsRole,
}
}
diff --git a/coderd/idpsync/organization.go b/coderd/idpsync/organization.go
index 66d8ab08495cc..12d79bc047776 100644
--- a/coderd/idpsync/organization.go
+++ b/coderd/idpsync/organization.go
@@ -149,13 +149,13 @@ 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 updates
// will ever come from the OIDC provider.
- Field string
+ Field string `json:"field"`
// Mapping controls how organizations returned by the OIDC provider get mapped
- Mapping map[string][]uuid.UUID
+ Mapping map[string][]uuid.UUID `json:"mapping"`
// AssignDefault will ensure all users that authenticate will be
// placed into the default organization. This is mostly a hack to support
// legacy deployments.
- AssignDefault bool
+ AssignDefault bool `json:"assign_default"`
}
func (s *OrganizationSyncSettings) Set(v string) error {
diff --git a/codersdk/audit.go b/codersdk/audit.go
index 9fe51e5f24a5f..307eeb275b61c 100644
--- a/codersdk/audit.go
+++ b/codersdk/audit.go
@@ -30,10 +30,13 @@ const (
ResourceTypeOrganization ResourceType = "organization"
ResourceTypeOAuth2ProviderApp ResourceType = "oauth2_provider_app"
// nolint:gosec // This is not a secret.
- ResourceTypeOAuth2ProviderAppSecret ResourceType = "oauth2_provider_app_secret"
- ResourceTypeCustomRole ResourceType = "custom_role"
- ResourceTypeOrganizationMember = "organization_member"
- ResourceTypeNotificationTemplate = "notification_template"
+ ResourceTypeOAuth2ProviderAppSecret ResourceType = "oauth2_provider_app_secret"
+ ResourceTypeCustomRole ResourceType = "custom_role"
+ ResourceTypeOrganizationMember ResourceType = "organization_member"
+ ResourceTypeNotificationTemplate ResourceType = "notification_template"
+ ResourceTypeIdpSyncSettingsOrganization ResourceType = "idp_sync_settings_organization"
+ ResourceTypeIdpSyncSettingsGroup ResourceType = "idp_sync_settings_group"
+ ResourceTypeIdpSyncSettingsRole ResourceType = "idp_sync_settings_role"
)
func (r ResourceType) FriendlyString() string {
@@ -78,6 +81,12 @@ func (r ResourceType) FriendlyString() string {
return "organization member"
case ResourceTypeNotificationTemplate:
return "notification template"
+ case ResourceTypeIdpSyncSettingsOrganization:
+ return "settings"
+ case ResourceTypeIdpSyncSettingsGroup:
+ return "settings"
+ case ResourceTypeIdpSyncSettingsRole:
+ return "settings"
default:
return "unknown"
}
diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index 8f39e130a7dad..092cb5fba6456 100644
--- a/docs/admin/security/audit-logs.md
+++ b/docs/admin/security/audit-logs.md
@@ -16,6 +16,7 @@ We track the following resources:
| AuditableOrganizationMember
|
Field | Tracked |
---|
created_at | true |
organization_id | false |
roles | true |
updated_at | true |
user_id | true |
username | true |
|
| CustomRole
| Field | Tracked |
---|
created_at | false |
display_name | true |
id | false |
name | true |
org_permissions | true |
organization_id | false |
site_permissions | true |
updated_at | false |
user_permissions | true |
|
| GitSSHKey
create | Field | Tracked |
---|
created_at | false |
private_key | true |
public_key | true |
updated_at | false |
user_id | true |
|
+| GroupSyncSettings
| Field | Tracked |
---|
auto_create_missing_groups | true |
field | true |
legacy_group_name_mapping | false |
mapping | true |
regex_filter | true |
|
| HealthSettings
| Field | Tracked |
---|
dismissed_healthchecks | true |
id | false |
|
| License
create, delete | Field | Tracked |
---|
exp | true |
id | false |
jwt | false |
uploaded_at | true |
uuid | true |
|
| NotificationTemplate
| Field | Tracked |
---|
actions | true |
body_template | true |
group | true |
id | false |
kind | true |
method | true |
name | true |
title_template | true |
|
@@ -23,6 +24,8 @@ We track the following resources:
| OAuth2ProviderApp
| Field | Tracked |
---|
callback_url | true |
created_at | false |
icon | true |
id | false |
name | true |
updated_at | false |
|
| OAuth2ProviderAppSecret
| Field | Tracked |
---|
app_id | false |
created_at | false |
display_secret | false |
hashed_secret | false |
id | false |
last_used_at | false |
secret_prefix | false |
|
| Organization
| Field | Tracked |
---|
created_at | false |
description | true |
display_name | true |
icon | true |
id | false |
is_default | true |
name | true |
updated_at | true |
|
+| OrganizationSyncSettings
| Field | Tracked |
---|
assign_default | true |
field | true |
mapping | true |
|
+| RoleSyncSettings
| Field | Tracked |
---|
field | true |
mapping | true |
|
| Template
write, delete | Field | Tracked |
---|
active_version_id | true |
activity_bump | true |
allow_user_autostart | true |
allow_user_autostop | true |
allow_user_cancel_workspace_jobs | true |
autostart_block_days_of_week | true |
autostop_requirement_days_of_week | true |
autostop_requirement_weeks | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_username | false |
default_ttl | true |
deleted | false |
deprecated | true |
description | true |
display_name | true |
failure_ttl | true |
group_acl | true |
icon | true |
id | true |
max_port_sharing_level | true |
name | true |
organization_display_name | false |
organization_icon | false |
organization_id | false |
organization_name | false |
provisioner | true |
require_active_version | true |
time_til_dormant | true |
time_til_dormant_autodelete | true |
updated_at | false |
user_acl | true |
|
| TemplateVersion
create, write | Field | Tracked |
---|
archived | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_username | false |
external_auth_providers | false |
id | true |
job_id | false |
message | false |
name | true |
organization_id | false |
readme | true |
source_example_id | false |
template_id | true |
updated_at | false |
|
| User
create, write, delete | Field | Tracked |
---|
avatar_url | false |
created_at | false |
deleted | true |
email | true |
github_com_user_id | false |
hashed_one_time_passcode | false |
hashed_password | true |
id | true |
last_seen_at | false |
login_type | true |
name | true |
one_time_passcode_expires_at | true |
quiet_hours_schedule | true |
rbac_roles | true |
status | true |
theme_preference | false |
updated_at | false |
username | true |
|
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index b124e7be93b26..6b91a64d02789 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -4777,25 +4777,30 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
#### Enumerated Values
-| Value |
-| ---------------------------- |
-| `template` |
-| `template_version` |
-| `user` |
-| `workspace` |
-| `workspace_build` |
-| `git_ssh_key` |
-| `api_key` |
-| `group` |
-| `license` |
-| `convert_login` |
-| `health_settings` |
-| `notifications_settings` |
-| `workspace_proxy` |
-| `organization` |
-| `oauth2_provider_app` |
-| `oauth2_provider_app_secret` |
-| `custom_role` |
+| Value |
+| -------------------------------- |
+| `template` |
+| `template_version` |
+| `user` |
+| `workspace` |
+| `workspace_build` |
+| `git_ssh_key` |
+| `api_key` |
+| `group` |
+| `license` |
+| `convert_login` |
+| `health_settings` |
+| `notifications_settings` |
+| `workspace_proxy` |
+| `organization` |
+| `oauth2_provider_app` |
+| `oauth2_provider_app_secret` |
+| `custom_role` |
+| `organization_member` |
+| `notification_template` |
+| `idp_sync_settings_organization` |
+| `idp_sync_settings_group` |
+| `idp_sync_settings_role` |
## codersdk.Response
diff --git a/enterprise/audit/diff.go b/enterprise/audit/diff.go
index 07cd8a5fdcb87..8196238ecc841 100644
--- a/enterprise/audit/diff.go
+++ b/enterprise/audit/diff.go
@@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"reflect"
+ "strings"
"github.com/google/uuid"
"golang.org/x/xerrors"
@@ -49,6 +50,7 @@ func diffValues(left, right any, table Table) audit.Map {
)
diffName := field.FieldType.Tag.Get("json")
+ diffName = strings.TrimSuffix(diffName, ",omitempty")
atype, ok := diffKey[diffName]
if !ok {
diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go
index 4f27d8fe06b64..4bbeefdf01e09 100644
--- a/enterprise/audit/table.go
+++ b/enterprise/audit/table.go
@@ -5,8 +5,10 @@ import (
"os"
"reflect"
"runtime"
+ "strings"
"github.com/coder/coder/v2/coderd/database"
+ "github.com/coder/coder/v2/coderd/idpsync"
"github.com/coder/coder/v2/codersdk"
)
@@ -286,6 +288,23 @@ var auditableResourcesTypes = map[any]map[string]Action{
"method": ActionTrack,
"kind": ActionTrack,
},
+ &idpsync.OrganizationSyncSettings{}: {
+ "field": ActionTrack,
+ "mapping": ActionTrack,
+ "assign_default": ActionTrack,
+ },
+ &idpsync.GroupSyncSettings{}: {
+ "field": ActionTrack,
+ "mapping": ActionTrack,
+ "regex_filter": ActionTrack,
+ "auto_create_missing_groups": ActionTrack,
+ // Configured in env vars
+ "legacy_group_name_mapping": ActionIgnore,
+ },
+ &idpsync.RoleSyncSettings{}: {
+ "field": ActionTrack,
+ "mapping": ActionTrack,
+ },
}
// auditMap converts a map of struct pointers to a map of struct names as
@@ -335,6 +354,7 @@ func entry(v any, f map[string]Action) (string, map[string]Action) {
// This field is explicitly ignored.
continue
}
+ jsonTag = strings.TrimSuffix(jsonTag, ",omitempty")
if _, ok := fcpy[jsonTag]; !ok {
_, _ = fmt.Fprintf(os.Stderr, "ERROR: Audit table entry missing action for field %q in type %q\nPlease update the auditable resource types in: %s\n", d.FieldType.Name, name, self())
//nolint:revive
diff --git a/enterprise/coderd/idpsync.go b/enterprise/coderd/idpsync.go
index 087266462df26..e7346f8406844 100644
--- a/enterprise/coderd/idpsync.go
+++ b/enterprise/coderd/idpsync.go
@@ -6,6 +6,8 @@ import (
"github.com/google/uuid"
+ "github.com/coder/coder/v2/coderd/audit"
+ "github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
@@ -56,6 +58,16 @@ func (api *API) groupIDPSyncSettings(rw http.ResponseWriter, r *http.Request) {
func (api *API) patchGroupIDPSyncSettings(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)
@@ -83,7 +95,14 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
//nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx)
- err := api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, idpsync.GroupSyncSettings{
+ existing, err := api.IDPSync.GroupSyncSettings(sysCtx, org.ID, api.Database)
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+ aReq.Old = *existing
+
+ err = api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, idpsync.GroupSyncSettings{
Field: req.Field,
Mapping: req.Mapping,
RegexFilter: req.RegexFilter,
@@ -101,6 +120,7 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
return
}
+ aReq.New = *settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.GroupSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
@@ -151,6 +171,16 @@ func (api *API) roleIDPSyncSettings(rw http.ResponseWriter, r *http.Request) {
func (api *API) patchRoleIDPSyncSettings(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)
@@ -164,7 +194,14 @@ func (api *API) patchRoleIDPSyncSettings(rw http.ResponseWriter, r *http.Request
//nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx)
- err := api.IDPSync.UpdateRoleSettings(sysCtx, org.ID, api.Database, idpsync.RoleSyncSettings{
+ existing, err := api.IDPSync.RoleSyncSettings(sysCtx, org.ID, api.Database)
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+ aReq.Old = *existing
+
+ err = api.IDPSync.UpdateRoleSettings(sysCtx, org.ID, api.Database, idpsync.RoleSyncSettings{
Field: req.Field,
Mapping: req.Mapping,
})
@@ -179,6 +216,7 @@ func (api *API) patchRoleIDPSyncSettings(rw http.ResponseWriter, r *http.Request
return
}
+ aReq.New = *settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.RoleSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
@@ -226,6 +264,14 @@ func (api *API) organizationIDPSyncSettings(rw http.ResponseWriter, r *http.Requ
// @Router /settings/idpsync/organization [patch]
func (api *API) patchOrganizationIDPSyncSettings(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)
@@ -239,7 +285,14 @@ func (api *API) patchOrganizationIDPSyncSettings(rw http.ResponseWriter, r *http
//nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx)
- err := api.IDPSync.UpdateOrganizationSettings(sysCtx, api.Database, idpsync.OrganizationSyncSettings{
+ existing, err := api.IDPSync.OrganizationSyncSettings(sysCtx, api.Database)
+ if err != nil {
+ httpapi.InternalServerError(rw, err)
+ return
+ }
+ aReq.Old = *existing
+
+ err = api.IDPSync.UpdateOrganizationSettings(sysCtx, api.Database, idpsync.OrganizationSyncSettings{
Field: req.Field,
// We do not check if the mappings point to actual organizations.
Mapping: req.Mapping,
@@ -256,6 +309,7 @@ func (api *API) patchOrganizationIDPSyncSettings(rw http.ResponseWriter, r *http
return
}
+ aReq.New = *settings
httpapi.Write(ctx, rw, http.StatusOK, codersdk.OrganizationSyncSettings{
Field: settings.Field,
Mapping: settings.Mapping,
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 5d840d763af78..c605268c9d920 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -1548,15 +1548,9 @@ export interface ResolveAutostartResponse {
}
// From codersdk/audit.go
-export type ResourceType = "api_key" | "convert_login" | "custom_role" | "git_ssh_key" | "group" | "health_settings" | "license" | "notifications_settings" | "oauth2_provider_app" | "oauth2_provider_app_secret" | "organization" | "template" | "template_version" | "user" | "workspace" | "workspace_build" | "workspace_proxy";
+export type ResourceType = "api_key" | "convert_login" | "custom_role" | "git_ssh_key" | "group" | "health_settings" | "idp_sync_settings_group" | "idp_sync_settings_organization" | "idp_sync_settings_role" | "license" | "notification_template" | "notifications_settings" | "oauth2_provider_app" | "oauth2_provider_app_secret" | "organization" | "organization_member" | "template" | "template_version" | "user" | "workspace" | "workspace_build" | "workspace_proxy";
-// From codersdk/audit.go
-export const ResourceTypeNotificationTemplate = "notification_template";
-
-// From codersdk/audit.go
-export const ResourceTypeOrganizationMember = "organization_member";
-
-export const ResourceTypes: ResourceType[] = ["api_key", "convert_login", "custom_role", "git_ssh_key", "group", "health_settings", "license", "notifications_settings", "oauth2_provider_app", "oauth2_provider_app_secret", "organization", "template", "template_version", "user", "workspace", "workspace_build", "workspace_proxy"];
+export const ResourceTypes: ResourceType[] = ["api_key", "convert_login", "custom_role", "git_ssh_key", "group", "health_settings", "idp_sync_settings_group", "idp_sync_settings_organization", "idp_sync_settings_role", "license", "notification_template", "notifications_settings", "oauth2_provider_app", "oauth2_provider_app_secret", "organization", "organization_member", "template", "template_version", "user", "workspace", "workspace_build", "workspace_proxy"];
// From codersdk/client.go
export interface Response {
diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/auditUtils.ts b/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/auditUtils.ts
index 7c0696a9afcbb..f07a69985d0d8 100644
--- a/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/auditUtils.ts
+++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogDiff/auditUtils.ts
@@ -23,3 +23,29 @@ export const determineGroupDiff = (auditLogDiff: AuditDiff): AuditDiff => {
},
};
};
+
+/**
+ *
+ * @param auditLogDiff
+ * @returns a diff with the 'mappings' as a JSON string. Otherwise, it is [Object object]
+ */
+export const determineIdPSyncMappingDiff = (
+ auditLogDiff: AuditDiff,
+): AuditDiff => {
+ const old = auditLogDiff.mapping?.old as Record | undefined;
+ const new_ = auditLogDiff.mapping?.new as
+ | Record
+ | undefined;
+ if (!old || !new_) {
+ return auditLogDiff;
+ }
+
+ return {
+ ...auditLogDiff,
+ mapping: {
+ old: JSON.stringify(old),
+ new: JSON.stringify(new_),
+ secret: auditLogDiff.mapping?.secret,
+ },
+ };
+};
diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx
index db8e7e4537cc4..909fb7cf5646e 100644
--- a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx
+++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx
@@ -16,7 +16,10 @@ import type { ThemeRole } from "theme/roles";
import userAgentParser from "ua-parser-js";
import { AuditLogDescription } from "./AuditLogDescription/AuditLogDescription";
import { AuditLogDiff } from "./AuditLogDiff/AuditLogDiff";
-import { determineGroupDiff } from "./AuditLogDiff/auditUtils";
+import {
+ determineGroupDiff,
+ determineIdPSyncMappingDiff,
+} from "./AuditLogDiff/auditUtils";
const httpStatusColor = (httpStatus: number): ThemeRole => {
// Treat server errors (500) as errors
@@ -59,6 +62,14 @@ export const AuditLogRow: FC = ({
auditDiff = determineGroupDiff(auditLog.diff);
}
+ if (
+ auditLog.resource_type === "idp_sync_settings_organization" ||
+ auditLog.resource_type === "idp_sync_settings_group" ||
+ auditLog.resource_type === "idp_sync_settings_role"
+ ) {
+ auditDiff = determineIdPSyncMappingDiff(auditLog.diff);
+ }
+
const toggle = () => {
if (shouldDisplayDiff) {
setIsDiffOpen((v) => !v);