diff --git a/coderd/audit/request.go b/coderd/audit/request.go index 55c4599cdc61a..ea70aee9e08a3 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -24,6 +24,10 @@ type RequestParams struct { Request *http.Request Action database.AuditAction AdditionalFields json.RawMessage + + // specific to Group resource patch requests + HasGroupMemberChange bool + GroupMemberLists json.RawMessage } type Request[T Auditable] struct { @@ -144,6 +148,12 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request if sw.Status < 400 { diff := Diff(p.Audit, req.Old, req.New) + // Group resource types may have group member changes. + // We track diffs of this nature differently as GroupMember is a distinct table. + if p.HasGroupMemberChange { + diff = addGroupMemberDiff(logCtx, diff, p.GroupMemberLists, p.Log) + } + var err error diffRaw, err = json.Marshal(diff) if err != nil { @@ -238,3 +248,30 @@ func parseIP(ipStr string) pqtype.Inet { Valid: ip != nil, } } + +type GroupMemberLists struct { + OldGroupMembers []string + NewGroupMembers []string +} + +// Adds a 'members' key to Group resource diffs +// in order to capture the addition or removal of group members +func addGroupMemberDiff(logCtx context.Context, diff Map, groupMemberLists json.RawMessage, logger slog.Logger) Map { + var ( + groupMemberBytes = []byte(groupMemberLists) + members GroupMemberLists + err = json.Unmarshal(groupMemberBytes, &members) + ) + + if err == nil { + diff["members"] = OldNew{ + Old: members.OldGroupMembers, + New: members.NewGroupMembers, + Secret: false, + } + } else { + logger.Warn(logCtx, "marshal group member diff", slog.Error(err)) + } + + return diff +} diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index ec566c8569745..bfc64ba452abc 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -2,6 +2,7 @@ package coderd import ( "database/sql" + "encoding/json" "fmt" "net/http" @@ -14,6 +15,7 @@ import ( "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/httpmw" "github.com/coder/coder/coderd/rbac" + "github.com/coder/coder/coderd/util/slice" "github.com/coder/coder/codersdk" ) @@ -72,16 +74,44 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request) func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { var ( - ctx = r.Context() - group = httpmw.GroupParam(r) - auditor = api.AGPL.Auditor.Load() - aReq, commitAudit = audit.InitRequest[database.Group](rw, &audit.RequestParams{ - Audit: *auditor, - Log: api.Logger, - Request: r, - Action: database.AuditActionWrite, - }) + ctx = r.Context() + group = httpmw.GroupParam(r) + auditor = api.AGPL.Auditor.Load() ) + + var req codersdk.PatchGroupRequest + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + var ( + groupResourceInfo = make(map[string][]string) + hasGroupMemberChange = len(req.AddUsers) != 0 || len(req.RemoveUsers) != 0 + ) + + if hasGroupMemberChange { + currentMembers, currentMembersErr := api.Database.GetGroupMembers(ctx, group.ID) + if currentMembersErr != nil { + httpapi.InternalServerError(rw, currentMembersErr) + return + } + + groupResourceInfo = createMemberMap(req, groupResourceInfo, currentMembers) + } + + wriBytes, marshalErr := json.Marshal(groupResourceInfo) + if marshalErr != nil { + httpapi.InternalServerError(rw, marshalErr) + } + aReq, commitAudit := audit.InitRequest[database.Group](rw, &audit.RequestParams{ + Audit: *auditor, + Log: api.Logger, + Request: r, + Action: database.AuditActionWrite, + HasGroupMemberChange: hasGroupMemberChange, + GroupMemberLists: wriBytes, + }) + defer commitAudit() aReq.Old = group @@ -90,11 +120,6 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { return } - var req codersdk.PatchGroupRequest - if !httpapi.Read(ctx, rw, r, &req) { - return - } - if req.Name != "" && req.Name == database.AllUsersGroup { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("%q is a reserved group name!", database.AllUsersGroup), @@ -375,3 +400,28 @@ func convertRole(role rbac.Role) codersdk.Role { Name: role.Name, } } + +// Creates a list of current group member IDs +// as well as a list of the patched group member IDs +// in order to form a diff for the group resource audit log entry +func createMemberMap(req codersdk.PatchGroupRequest, groupMemberMap map[string][]string, currentMembers []database.User) map[string][]string { + var oldMemberIds []string + for _, x := range currentMembers { + oldMemberIds = append(oldMemberIds, x.ID.String()) + } + + var newMemberIds []string + // Although we favor adding users over deleting them, + // users are added and deleted in separate requests on the FE, + // so we won't see duplicate IDs. + newMemberIds = append(newMemberIds, req.AddUsers...) + for _, x := range currentMembers { + if !slice.Contains(newMemberIds, x.ID.String()) && !slice.Contains(req.RemoveUsers, x.ID.String()) { + newMemberIds = append(newMemberIds, x.ID.String()) + } + } + + groupMemberMap["oldGroupMembers"] = oldMemberIds + groupMemberMap["newGroupMembers"] = newMemberIds + return groupMemberMap +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy