Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit6b68fbb

Browse files
Kira-PilotEmyrk
andauthored
feat: Auditing group members as part of group resource (#5730)
* added AuditableGroup type* added json tags* Anonymizing gGroup struct* adding support on the FE for nested group diffs* added type for GroupMember* Update coderd/database/modelmethods.goCo-authored-by: Steven Masley <Emyrk@users.noreply.github.com>* Update coderd/database/modelmethods.goCo-authored-by: Steven Masley <Emyrk@users.noreply.github.com>* fetching group members in group.delete* passing through right error* broke out into util function and added testsCo-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
1 parent56b9965 commit6b68fbb

File tree

10 files changed

+230
-27
lines changed

10 files changed

+230
-27
lines changed

‎coderd/audit.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,8 @@ func resourceTypeFromString(resourceTypeString string) string {
464464
returnresourceTypeString
465465
casecodersdk.ResourceTypeAPIKey:
466466
returnresourceTypeString
467+
casecodersdk.ResourceTypeGroup:
468+
returnresourceTypeString
467469
}
468470
return""
469471
}

‎coderd/audit/diff.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type Auditable interface {
1616
database.User|
1717
database.Workspace|
1818
database.GitSSHKey|
19-
database.Group|
20-
database.WorkspaceBuild
19+
database.WorkspaceBuild|
20+
database.AuditableGroup
2121
}
2222

2323
// Map is a map of changed fields in an audited resource. It maps field names to

‎coderd/audit/request.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ func ResourceTarget[T Auditable](tgt T) string {
6464
return""
6565
case database.GitSSHKey:
6666
returntyped.PublicKey
67-
case database.Group:
68-
returntyped.Name
67+
case database.AuditableGroup:
68+
returntyped.Group.Name
6969
default:
7070
panic(fmt.Sprintf("unknown resource %T",tgt))
7171
}
@@ -87,8 +87,8 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
8787
returntyped.ID
8888
case database.GitSSHKey:
8989
returntyped.UserID
90-
case database.Group:
91-
returntyped.ID
90+
case database.AuditableGroup:
91+
returntyped.Group.ID
9292
default:
9393
panic(fmt.Sprintf("unknown resource %T",tgt))
9494
}
@@ -110,7 +110,7 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
110110
returndatabase.ResourceTypeWorkspaceBuild
111111
case database.GitSSHKey:
112112
returndatabase.ResourceTypeGitSshKey
113-
case database.Group:
113+
case database.AuditableGroup:
114114
returndatabase.ResourceTypeGroup
115115
default:
116116
panic(fmt.Sprintf("unknown resource %T",tgt))

‎coderd/database/modelmethods.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,38 @@
11
package database
22

33
import (
4+
"sort"
5+
46
"github.com/coder/coder/coderd/rbac"
57
)
68

9+
typeAuditableGroupstruct {
10+
Group
11+
Members []GroupMember`json:"members"`
12+
}
13+
14+
// Auditable returns an object that can be used in audit logs.
15+
// Covers both group and group member changes.
16+
func (gGroup)Auditable(users []User)AuditableGroup {
17+
members:=make([]GroupMember,0,len(users))
18+
for_,u:=rangeusers {
19+
members=append(members,GroupMember{
20+
UserID:u.ID,
21+
GroupID:g.ID,
22+
})
23+
}
24+
25+
// consistent ordering
26+
sort.Slice(members,func(i,jint)bool {
27+
returnmembers[i].UserID.String()<members[j].UserID.String()
28+
})
29+
30+
returnAuditableGroup{
31+
Group:g,
32+
Members:members,
33+
}
34+
}
35+
736
constAllUsersGroup="Everyone"
837

938
func (sAPIKeyScope)ToRBAC() rbac.Scope {

‎enterprise/audit/table.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,6 @@ var AuditableResources = auditMap(map[any]map[string]Action{
105105
"ttl":ActionTrack,
106106
"last_used_at":ActionIgnore,
107107
},
108-
&database.Group{}: {
109-
"id":ActionTrack,
110-
"name":ActionTrack,
111-
"organization_id":ActionIgnore,// Never changes.
112-
"avatar_url":ActionTrack,
113-
"quota_allowance":ActionTrack,
114-
},
115108
// We don't show any diff for the WorkspaceBuild resource
116109
&database.WorkspaceBuild{}: {
117110
"id":ActionIgnore,
@@ -128,6 +121,14 @@ var AuditableResources = auditMap(map[any]map[string]Action{
128121
"reason":ActionIgnore,
129122
"daily_cost":ActionIgnore,
130123
},
124+
&database.AuditableGroup{}: {
125+
"id":ActionTrack,
126+
"name":ActionTrack,
127+
"organization_id":ActionIgnore,// Never changes.
128+
"avatar_url":ActionTrack,
129+
"quota_allowance":ActionTrack,
130+
"members":ActionTrack,
131+
},
131132
})
132133

133134
// auditMap converts a map of struct pointers to a map of struct names as

‎enterprise/coderd/groups.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
3232
ctx=r.Context()
3333
org=httpmw.OrganizationParam(r)
3434
auditor=api.AGPL.Auditor.Load()
35-
aReq,commitAudit=audit.InitRequest[database.Group](rw,&audit.RequestParams{
35+
aReq,commitAudit=audit.InitRequest[database.AuditableGroup](rw,&audit.RequestParams{
3636
Audit:*auditor,
3737
Log:api.Logger,
3838
Request:r,
@@ -75,7 +75,9 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
7575
httpapi.InternalServerError(rw,err)
7676
return
7777
}
78-
aReq.New=group
78+
79+
varemptyUsers []database.User
80+
aReq.New=group.Auditable(emptyUsers)
7981

8082
httpapi.Write(ctx,rw,http.StatusCreated,convertGroup(group,nil))
8183
}
@@ -93,15 +95,22 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
9395
ctx=r.Context()
9496
group=httpmw.GroupParam(r)
9597
auditor=api.AGPL.Auditor.Load()
96-
aReq,commitAudit=audit.InitRequest[database.Group](rw,&audit.RequestParams{
98+
aReq,commitAudit=audit.InitRequest[database.AuditableGroup](rw,&audit.RequestParams{
9799
Audit:*auditor,
98100
Log:api.Logger,
99101
Request:r,
100102
Action:database.AuditActionWrite,
101103
})
102104
)
103105
defercommitAudit()
104-
aReq.Old=group
106+
107+
currentMembers,currentMembersErr:=api.Database.GetGroupMembers(ctx,group.ID)
108+
ifcurrentMembersErr!=nil {
109+
httpapi.InternalServerError(rw,currentMembersErr)
110+
return
111+
}
112+
113+
aReq.Old=group.Auditable(currentMembers)
105114

106115
if!api.Authorize(r,rbac.ActionUpdate,group) {
107116
http.NotFound(rw,r)
@@ -233,15 +242,15 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
233242
return
234243
}
235244

236-
members,err:=api.Database.GetGroupMembers(ctx,group.ID)
245+
patchedMembers,err:=api.Database.GetGroupMembers(ctx,group.ID)
237246
iferr!=nil {
238247
httpapi.InternalServerError(rw,err)
239248
return
240249
}
241250

242-
aReq.New=group
251+
aReq.New=group.Auditable(patchedMembers)
243252

244-
httpapi.Write(ctx,rw,http.StatusOK,convertGroup(group,members))
253+
httpapi.Write(ctx,rw,http.StatusOK,convertGroup(group,patchedMembers))
245254
}
246255

247256
// @Summary Delete group by name
@@ -257,15 +266,22 @@ func (api *API) deleteGroup(rw http.ResponseWriter, r *http.Request) {
257266
ctx=r.Context()
258267
group=httpmw.GroupParam(r)
259268
auditor=api.AGPL.Auditor.Load()
260-
aReq,commitAudit=audit.InitRequest[database.Group](rw,&audit.RequestParams{
269+
aReq,commitAudit=audit.InitRequest[database.AuditableGroup](rw,&audit.RequestParams{
261270
Audit:*auditor,
262271
Log:api.Logger,
263272
Request:r,
264273
Action:database.AuditActionDelete,
265274
})
266275
)
267276
defercommitAudit()
268-
aReq.Old=group
277+
278+
groupMembers,getMembersErr:=api.Database.GetGroupMembers(ctx,group.ID)
279+
ifgetMembersErr!=nil {
280+
httpapi.InternalServerError(rw,getMembersErr)
281+
return
282+
}
283+
284+
aReq.Old=group.Auditable(groupMembers)
269285

270286
if!api.Authorize(r,rbac.ActionDelete,group) {
271287
httpapi.ResourceNotFound(rw)

‎site/src/components/AuditLogRow/AuditLogDiff.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AuditLog } from "api/typesGenerated"
33
import{colors}from"theme/colors"
44
import{MONOSPACE_FONT_FAMILY}from"theme/constants"
55
import{combineClasses}from"util/combineClasses"
6+
import{FC}from"react"
67

78
constgetDiffValue=(value:unknown):string=>{
89
if(typeofvalue==="string"){
@@ -21,9 +22,7 @@ const getDiffValue = (value: unknown): string => {
2122
returnvalue.toString()
2223
}
2324

24-
exportconstAuditLogDiff:React.FC<{diff:AuditLog["diff"]}>=({
25-
diff,
26-
})=>{
25+
exportconstAuditLogDiff:FC<{diff:AuditLog["diff"]}>=({ diff})=>{
2726
conststyles=useStyles()
2827
constdiffEntries=Object.entries(diff)
2928

‎site/src/components/AuditLogRow/AuditLogRow.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import userAgentParser from "ua-parser-js"
1616
import{AuditLogDiff}from"./AuditLogDiff"
1717
importi18nextfrom"i18next"
1818
import{AuditLogDescription}from"./AuditLogDescription"
19+
import{determineGroupDiff}from"./auditUtils"
1920

2021
consthttpStatusColor=(httpStatus:number):PaletteIndex=>{
2122
if(httpStatus>=300&&httpStatus<500){
@@ -49,6 +50,13 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
4950
?`${browser.name}${browser.version}`
5051
:t("auditLog:table.logRow.notAvailable")
5152

53+
letauditDiff=auditLog.diff
54+
55+
// groups have nested diffs (group members)
56+
if(auditLog.resource_type==="group"){
57+
auditDiff=determineGroupDiff(auditLog.diff)
58+
}
59+
5260
consttoggle=()=>{
5361
if(shouldDisplayDiff){
5462
setIsDiffOpen((v)=>!v)
@@ -153,7 +161,7 @@ export const AuditLogRow: React.FC<AuditLogRowProps> = ({
153161

154162
{shouldDisplayDiff&&(
155163
<Collapsein={isDiffOpen}>
156-
<AuditLogDiffdiff={auditLog.diff}/>
164+
<AuditLogDiffdiff={auditDiff}/>
157165
</Collapse>
158166
)}
159167
</TableCell>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import{determineGroupDiff}from"./auditUtils"
2+
3+
constauditDiffForNewGroup={
4+
id:{
5+
old:"",
6+
new:"e22e0eb9-625a-468b-b962-269b19473789",
7+
secret:false,
8+
},
9+
members:{
10+
new:[],
11+
secret:false,
12+
},
13+
name:{
14+
old:"",
15+
new:"another-test-group",
16+
secret:false,
17+
},
18+
}
19+
20+
constauditDiffForAddedGroupMember={
21+
members:{
22+
old:[],
23+
new:[
24+
{
25+
group_id:"e22e0eb9-625a-468b-b962-269b19473789",
26+
user_id:"cea4c2b0-6373-4858-b26a-df3cbfce8845",
27+
},
28+
],
29+
secret:false,
30+
},
31+
}
32+
33+
constauditDiffForRemovedGroupMember={
34+
members:{
35+
old:[
36+
{
37+
group_id:"25793395-b093-4a3c-a473-9ecf9b243478",
38+
user_id:"84d1cd5a-17e1-4022-898c-52e64256e737",
39+
},
40+
{
41+
group_id:"25793395-b093-4a3c-a473-9ecf9b243478",
42+
user_id:"cea4c2b0-6373-4858-b26a-df3cbfce8845",
43+
},
44+
],
45+
new:[
46+
{
47+
group_id:"25793395-b093-4a3c-a473-9ecf9b243478",
48+
user_id:"84d1cd5a-17e1-4022-898c-52e64256e737",
49+
},
50+
],
51+
secret:false,
52+
},
53+
}
54+
55+
constAuditDiffForDeletedGroup={
56+
id:{
57+
old:"25793395-b093-4a3c-a473-9ecf9b243478",
58+
new:"",
59+
secret:false,
60+
},
61+
members:{
62+
old:[
63+
{
64+
group_id:"25793395-b093-4a3c-a473-9ecf9b243478",
65+
user_id:"84d1cd5a-17e1-4022-898c-52e64256e737",
66+
},
67+
],
68+
secret:false,
69+
},
70+
name:{
71+
old:"test-group",
72+
new:"",
73+
secret:false,
74+
},
75+
}
76+
77+
describe("determineAuditDiff",()=>{
78+
it("auditDiffForNewGroup",()=>{
79+
// there should be no change as members are not added when a group is created
80+
expect(determineGroupDiff(auditDiffForNewGroup)).toEqual(
81+
auditDiffForNewGroup,
82+
)
83+
})
84+
85+
it("auditDiffForAddedGroupMember",()=>{
86+
constresult={
87+
members:{
88+
...auditDiffForAddedGroupMember.members,
89+
new:["cea4c2b0-6373-4858-b26a-df3cbfce8845"],
90+
},
91+
}
92+
93+
expect(determineGroupDiff(auditDiffForAddedGroupMember)).toEqual(result)
94+
})
95+
96+
it("auditDiffForRemovedGroupMember",()=>{
97+
constresult={
98+
members:{
99+
...auditDiffForRemovedGroupMember.members,
100+
old:[
101+
"84d1cd5a-17e1-4022-898c-52e64256e737",
102+
"cea4c2b0-6373-4858-b26a-df3cbfce8845",
103+
],
104+
new:["84d1cd5a-17e1-4022-898c-52e64256e737"],
105+
},
106+
}
107+
108+
expect(determineGroupDiff(auditDiffForRemovedGroupMember)).toEqual(result)
109+
})
110+
111+
it("AuditDiffForDeletedGroup",()=>{
112+
constresult={
113+
...AuditDiffForDeletedGroup,
114+
members:{
115+
...AuditDiffForDeletedGroup.members,
116+
old:["84d1cd5a-17e1-4022-898c-52e64256e737"],
117+
},
118+
}
119+
120+
expect(determineGroupDiff(AuditDiffForDeletedGroup)).toEqual(result)
121+
})
122+
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp