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

Commit889daf2

Browse files
authored
feat(enterprise): add auditing to SCIM (#13614)
1 parentc4656d7 commit889daf2

File tree

6 files changed

+131
-18
lines changed

6 files changed

+131
-18
lines changed

‎coderd/audit/request.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type RequestParams struct {
3131
OrganizationID uuid.UUID
3232
Request*http.Request
3333
Action database.AuditAction
34-
AdditionalFieldsjson.RawMessage
34+
AdditionalFieldsinterface{}
3535
}
3636

3737
typeRequest[TAuditable]struct {
@@ -283,8 +283,15 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
283283
}
284284
}
285285

286-
ifp.AdditionalFields==nil {
287-
p.AdditionalFields=json.RawMessage("{}")
286+
additionalFieldsRaw:=json.RawMessage("{}")
287+
288+
ifp.AdditionalFields!=nil {
289+
data,err:=json.Marshal(p.AdditionalFields)
290+
iferr!=nil {
291+
p.Log.Warn(logCtx,"marshal additional fields",slog.Error(err))
292+
}else {
293+
additionalFieldsRaw=json.RawMessage(data)
294+
}
288295
}
289296

290297
varuserID uuid.UUID
@@ -319,7 +326,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
319326
Diff:diffRaw,
320327
StatusCode:int32(sw.Status),
321328
RequestID:httpmw.RequestID(p.Request),
322-
AdditionalFields:p.AdditionalFields,
329+
AdditionalFields:additionalFieldsRaw,
323330
OrganizationID:requireOrgID[T](logCtx,p.OrganizationID,p.Log),
324331
}
325332
err:=p.Audit.Export(ctx,auditLog)

‎coderd/workspaces.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,17 +361,12 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
361361
}
362362
)
363363

364-
wriBytes,err:=json.Marshal(workspaceResourceInfo)
365-
iferr!=nil {
366-
api.Logger.Warn(ctx,"marshal workspace owner name")
367-
}
368-
369364
aReq,commitAudit:=audit.InitRequest[database.Workspace](rw,&audit.RequestParams{
370365
Audit:*auditor,
371366
Log:api.Logger,
372367
Request:r,
373368
Action:database.AuditActionCreate,
374-
AdditionalFields:wriBytes,
369+
AdditionalFields:workspaceResourceInfo,
375370
OrganizationID:organization.ID,
376371
})
377372

‎enterprise/coderd/scim.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/xerrors"
1616

1717
agpl"github.com/coder/coder/v2/coderd"
18+
"github.com/coder/coder/v2/coderd/audit"
1819
"github.com/coder/coder/v2/coderd/database"
1920
"github.com/coder/coder/v2/coderd/database/dbauthz"
2021
"github.com/coder/coder/v2/coderd/database/dbtime"
@@ -118,6 +119,11 @@ type SCIMUser struct {
118119
}`json:"meta"`
119120
}
120121

122+
varSCIMAuditAdditionalFields=map[string]string{
123+
"automatic_actor":"coder",
124+
"automatic_subsystem":"scim",
125+
}
126+
121127
// scimPostUser creates a new user, or returns the existing user if it exists.
122128
//
123129
// @Summary SCIM 2.0: Create new user
@@ -135,6 +141,16 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
135141
return
136142
}
137143

144+
auditor:=*api.AGPL.Auditor.Load()
145+
aReq,commitAudit:=audit.InitRequest[database.User](rw,&audit.RequestParams{
146+
Audit:auditor,
147+
Log:api.Logger,
148+
Request:r,
149+
Action:database.AuditActionCreate,
150+
AdditionalFields:SCIMAuditAdditionalFields,
151+
})
152+
defercommitAudit()
153+
138154
varsUserSCIMUser
139155
err:=json.NewDecoder(r.Body).Decode(&sUser)
140156
iferr!=nil {
@@ -170,7 +186,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
170186

171187
ifsUser.Active&&dbUser.Status==database.UserStatusSuspended {
172188
//nolint:gocritic
173-
_,err=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
189+
newUser,err:=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
174190
ID:dbUser.ID,
175191
// The user will get transitioned to Active after logging in.
176192
Status:database.UserStatusDormant,
@@ -180,8 +196,13 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
180196
_=handlerutil.WriteError(rw,err)
181197
return
182198
}
199+
aReq.New=newUser
200+
}else {
201+
aReq.New=dbUser
183202
}
184203

204+
aReq.Old=dbUser
205+
185206
httpapi.Write(ctx,rw,http.StatusOK,sUser)
186207
return
187208
}
@@ -223,6 +244,8 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
223244
_=handlerutil.WriteError(rw,err)
224245
return
225246
}
247+
aReq.New=dbUser
248+
aReq.UserID=dbUser.ID
226249

227250
sUser.ID=dbUser.ID.String()
228251
sUser.UserName=dbUser.Username
@@ -248,6 +271,15 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
248271
return
249272
}
250273

274+
auditor:=*api.AGPL.Auditor.Load()
275+
aReq,commitAudit:=audit.InitRequest[database.User](rw,&audit.RequestParams{
276+
Audit:auditor,
277+
Log:api.Logger,
278+
Request:r,
279+
Action:database.AuditActionWrite,
280+
})
281+
defercommitAudit()
282+
251283
id:=chi.URLParam(r,"id")
252284

253285
varsUserSCIMUser
@@ -270,6 +302,8 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
270302
_=handlerutil.WriteError(rw,err)
271303
return
272304
}
305+
aReq.Old=dbUser
306+
aReq.UserID=dbUser.ID
273307

274308
varstatus database.UserStatus
275309
ifsUser.Active {
@@ -280,7 +314,7 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
280314
}
281315

282316
//nolint:gocritic // needed for SCIM
283-
_,err=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
317+
userNew,err:=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
284318
ID:dbUser.ID,
285319
Status:status,
286320
UpdatedAt:dbtime.Now(),
@@ -289,6 +323,7 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
289323
_=handlerutil.WriteError(rw,err)
290324
return
291325
}
326+
aReq.New=userNew
292327

293328
httpapi.Write(ctx,rw,http.StatusOK,sUser)
294329
}

‎enterprise/coderd/scim_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313

14+
"github.com/coder/coder/v2/coderd/audit"
15+
"github.com/coder/coder/v2/coderd/coderdtest"
16+
"github.com/coder/coder/v2/coderd/database"
1417
"github.com/coder/coder/v2/codersdk"
1518
"github.com/coder/coder/v2/cryptorand"
1619
"github.com/coder/coder/v2/enterprise/coderd"
@@ -109,21 +112,34 @@ func TestScim(t *testing.T) {
109112
defercancel()
110113

111114
scimAPIKey:= []byte("hi")
115+
mockAudit:=audit.NewMock()
112116
client,_:=coderdenttest.New(t,&coderdenttest.Options{
113-
SCIMAPIKey:scimAPIKey,
117+
Options:&coderdtest.Options{Auditor:mockAudit},
118+
SCIMAPIKey:scimAPIKey,
119+
AuditLogging:true,
114120
LicenseOptions:&coderdenttest.LicenseOptions{
115121
AccountID:"coolin",
116122
Features: license.Features{
117-
codersdk.FeatureSCIM:1,
123+
codersdk.FeatureSCIM:1,
124+
codersdk.FeatureAuditLog:1,
118125
},
119126
},
120127
})
128+
mockAudit.ResetLogs()
121129

122130
sUser:=makeScimUser(t)
123131
res,err:=client.Request(ctx,"POST","/scim/v2/Users",sUser,setScimAuth(scimAPIKey))
124132
require.NoError(t,err)
125133
deferres.Body.Close()
126-
assert.Equal(t,http.StatusOK,res.StatusCode)
134+
require.Equal(t,http.StatusOK,res.StatusCode)
135+
136+
aLogs:=mockAudit.AuditLogs()
137+
require.Len(t,aLogs,1)
138+
af:=map[string]string{}
139+
err=json.Unmarshal([]byte(aLogs[0].AdditionalFields),&af)
140+
require.NoError(t,err)
141+
assert.Equal(t,coderd.SCIMAuditAdditionalFields,af)
142+
assert.Equal(t,database.AuditActionCreate,aLogs[0].Action)
127143

128144
userRes,err:=client.Users(ctx, codersdk.UsersRequest{Search:sUser.Emails[0].Value})
129145
require.NoError(t,err)
@@ -306,21 +322,27 @@ func TestScim(t *testing.T) {
306322
defercancel()
307323

308324
scimAPIKey:= []byte("hi")
325+
mockAudit:=audit.NewMock()
309326
client,_:=coderdenttest.New(t,&coderdenttest.Options{
310-
SCIMAPIKey:scimAPIKey,
327+
Options:&coderdtest.Options{Auditor:mockAudit},
328+
SCIMAPIKey:scimAPIKey,
329+
AuditLogging:true,
311330
LicenseOptions:&coderdenttest.LicenseOptions{
312331
AccountID:"coolin",
313332
Features: license.Features{
314-
codersdk.FeatureSCIM:1,
333+
codersdk.FeatureSCIM:1,
334+
codersdk.FeatureAuditLog:1,
315335
},
316336
},
317337
})
338+
mockAudit.ResetLogs()
318339

319340
sUser:=makeScimUser(t)
320341
res,err:=client.Request(ctx,"POST","/scim/v2/Users",sUser,setScimAuth(scimAPIKey))
321342
require.NoError(t,err)
322343
deferres.Body.Close()
323344
assert.Equal(t,http.StatusOK,res.StatusCode)
345+
mockAudit.ResetLogs()
324346

325347
err=json.NewDecoder(res.Body).Decode(&sUser)
326348
require.NoError(t,err)
@@ -333,6 +355,10 @@ func TestScim(t *testing.T) {
333355
_=res.Body.Close()
334356
assert.Equal(t,http.StatusOK,res.StatusCode)
335357

358+
aLogs:=mockAudit.AuditLogs()
359+
require.Len(t,aLogs,1)
360+
assert.Equal(t,database.AuditActionWrite,aLogs[0].Action)
361+
336362
userRes,err:=client.Users(ctx, codersdk.UsersRequest{Search:sUser.Emails[0].Value})
337363
require.NoError(t,err)
338364
require.Len(t,userRes.Users,1)

‎site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.stories.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,45 @@ export const UnsuccessfulLoginForUnknownUser: Story = {
5656
auditLog:MockAuditLogUnsuccessfulLoginKnownUser,
5757
},
5858
};
59+
60+
exportconstCreateUser:Story={
61+
args:{
62+
auditLog:{
63+
...MockAuditLog,
64+
resource_type:"user",
65+
resource_target:"colin",
66+
description:"{user} created user {target}",
67+
},
68+
},
69+
};
70+
71+
exportconstSCIMCreateUser:Story={
72+
args:{
73+
auditLog:{
74+
...MockAuditLog,
75+
resource_type:"user",
76+
resource_target:"colin",
77+
description:"{user} created user {target}",
78+
additional_fields:{
79+
automatic_actor:"coder",
80+
automatic_subsystem:"scim",
81+
},
82+
},
83+
},
84+
};
85+
86+
exportconstSCIMUpdateUser:Story={
87+
args:{
88+
auditLog:{
89+
...MockAuditLog,
90+
action:"write",
91+
resource_type:"user",
92+
resource_target:"colin",
93+
description:"{user} updated user {target}",
94+
additional_fields:{
95+
automatic_actor:"coder",
96+
automatic_subsystem:"scim",
97+
},
98+
},
99+
},
100+
};

‎site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
1212
auditLog,
1313
})=>{
1414
lettarget=auditLog.resource_target.trim();
15-
constuser=auditLog.user?.username.trim();
15+
letuser=auditLog.user?.username.trim();
1616

1717
if(auditLog.resource_type==="workspace_build"){
1818
return<BuildAuditDescriptionauditLog={auditLog}/>;
@@ -23,6 +23,14 @@ export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
2323
target="";
2424
}
2525

26+
// This occurs when SCIM creates a user.
27+
if(
28+
auditLog.resource_type==="user"&&
29+
auditLog.additional_fields?.automatic_actor==="coder"
30+
){
31+
user="Coder automatically";
32+
}
33+
2634
consttruncatedDescription=auditLog.description
2735
.replace("{user}",`${user}`)
2836
.replace("{target}","");

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp