chore: audit log entries for all idp sync changes (#15919)

This commit is contained in:
Steven Masley
2025-01-02 15:02:04 -06:00
committed by GitHub
parent 761a19663f
commit 73ec6b2635
18 changed files with 259 additions and 64 deletions
+12 -2
View File
@@ -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": {
+12 -2
View File
@@ -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": {
+5 -1
View File
@@ -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
+30
View File
@@ -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))
}
+4 -1
View File
@@ -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 (
@@ -0,0 +1 @@
-- Nothing to do
@@ -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';
+29 -20
View File
@@ -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,
}
}
+3 -3
View File
@@ -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 {
+13 -4
View File
@@ -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"
}
+3
View File
@@ -16,6 +16,7 @@ We track the following resources:
| AuditableOrganizationMember<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>roles</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
| CustomRole<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>org_permissions</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>site_permissions</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_permissions</td><td>true</td></tr></tbody></table> |
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| GroupSyncSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>auto_create_missing_groups</td><td>true</td></tr><tr><td>field</td><td>true</td></tr><tr><td>legacy_group_name_mapping</td><td>false</td></tr><tr><td>mapping</td><td>true</td></tr><tr><td>regex_filter</td><td>true</td></tr></tbody></table> |
| HealthSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>dismissed_healthchecks</td><td>true</td></tr><tr><td>id</td><td>false</td></tr></tbody></table> |
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
| NotificationTemplate<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>actions</td><td>true</td></tr><tr><td>body_template</td><td>true</td></tr><tr><td>group</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>kind</td><td>true</td></tr><tr><td>method</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>title_template</td><td>true</td></tr></tbody></table> |
@@ -23,6 +24,8 @@ We track the following resources:
| OAuth2ProviderApp<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>callback_url</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| OAuth2ProviderAppSecret<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>app_id</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_secret</td><td>false</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>secret_prefix</td><td>false</td></tr></tbody></table> |
| Organization<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>is_default</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr></tbody></table> |
| OrganizationSyncSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>assign_default</td><td>true</td></tr><tr><td>field</td><td>true</td></tr><tr><td>mapping</td><td>true</td></tr></tbody></table> |
| RoleSyncSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>field</td><td>true</td></tr><tr><td>mapping</td><td>true</td></tr></tbody></table> |
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_display_name</td><td>false</td></tr><tr><td>organization_icon</td><td>false</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>organization_name</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>source_example_id</td><td>false</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>false</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
+24 -19
View File
@@ -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
+2
View File
@@ -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 {
+20
View File
@@ -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
+57 -3
View File
@@ -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,
+2 -8
View File
@@ -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 {
@@ -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<string, string[]> | undefined;
const new_ = auditLogDiff.mapping?.new as
| Record<string, string[]>
| undefined;
if (!old || !new_) {
return auditLogDiff;
}
return {
...auditLogDiff,
mapping: {
old: JSON.stringify(old),
new: JSON.stringify(new_),
secret: auditLogDiff.mapping?.secret,
},
};
};
@@ -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<AuditLogRowProps> = ({
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);