-
Notifications
You must be signed in to change notification settings - Fork 978
feat: add audit logs for dormancy events #15298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
e137e92
e84b893
388111b
850ee38
7d2b532
68adf8a
764bc25
7846882
cd77405
95ac736
b6c043d
b3269d1
3829773
6fdb233
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,10 +67,15 @@ INSERT INTO | |
created_at, | ||
updated_at, | ||
rbac_roles, | ||
login_type | ||
login_type, | ||
status | ||
) | ||
VALUES | ||
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *; | ||
($1, $2, $3, $4, $5, $6, $7, $8, $9, | ||
-- if the status passed in is empty, fallback to dormant, which is what | ||
-- we were doing before. | ||
COALESCE(NULLIF(@status::text, '')::user_status, 'dormant'::user_status) | ||
) RETURNING *; | ||
Comment on lines
+74
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we handle this as a special case? Shouldn't we expect the status to be provided? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't really want to leak the previous default behavior out into the API code. I think it makes more sense to do the default behavior here, which is what was happening before when it wasn't being inserted. |
||
|
||
-- name: UpdateUserProfile :one | ||
UPDATE | ||
|
@@ -286,7 +291,7 @@ SET | |
WHERE | ||
last_seen_at < @last_seen_after :: timestamp | ||
AND status = 'active'::user_status | ||
RETURNING id, email, last_seen_at; | ||
RETURNING id, email, username, last_seen_at; | ||
|
||
-- AllUserIDs returns all UserIDs regardless of user status or deletion. | ||
-- name: AllUserIDs :many | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -566,6 +566,7 @@ func (api *API) loginRequest(ctx context.Context, rw http.ResponseWriter, req co | |
} | ||
|
||
if user.Status == database.UserStatusDormant { | ||
oldUser := user | ||
//nolint:gocritic // System needs to update status of the user account (dormant -> active). | ||
user, err = api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(ctx), database.UpdateUserStatusParams{ | ||
ID: user.ID, | ||
|
@@ -579,6 +580,28 @@ func (api *API) loginRequest(ctx context.Context, rw http.ResponseWriter, req co | |
}) | ||
return user, rbac.Subject{}, false | ||
} | ||
|
||
af := map[string]string{ | ||
"automatic_actor": "coder", | ||
"automatic_subsystem": "dormancy", | ||
} | ||
|
||
wriBytes, err := json.Marshal(af) | ||
if err != nil { | ||
api.Logger.Error(ctx, "marshal additional fields for dormancy audit", slog.Error(err)) | ||
wriBytes = []byte("{}") | ||
} | ||
|
||
audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.User]{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't audit if we fail to update the user status. Also why is this a background audit if it's part of an API request? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally I don't really think we get anything valuable from generating an audit log if this fails. The main reason for failed audit logs is for logging users attempting to take an action for which they don't have permission, or similar. The status failing to update here doesn't really matter, it can only ever be a 500 error. Plus, it'll be captured by the main login audit anyways. I think |
||
Audit: *api.Auditor.Load(), | ||
Log: api.Logger, | ||
UserID: user.ID, | ||
Action: database.AuditActionWrite, | ||
Old: oldUser, | ||
New: user, | ||
Status: http.StatusOK, | ||
AdditionalFields: wriBytes, | ||
}) | ||
} | ||
|
||
subject, userStatus, err := httpmw.UserRBACSubject(ctx, api.Database, user.ID, rbac.ScopeAll) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,7 @@ import ( | |
"github.com/coder/coder/v2/coderd/searchquery" | ||
"github.com/coder/coder/v2/coderd/telemetry" | ||
"github.com/coder/coder/v2/coderd/userpassword" | ||
"github.com/coder/coder/v2/coderd/util/ptr" | ||
"github.com/coder/coder/v2/coderd/util/slice" | ||
"github.com/coder/coder/v2/codersdk" | ||
) | ||
|
@@ -192,9 +193,12 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { | |
Username: createUser.Username, | ||
Name: createUser.Name, | ||
Password: createUser.Password, | ||
UserStatus: ptr.Ref(codersdk.UserStatusActive), | ||
OrganizationIDs: []uuid.UUID{defaultOrg.ID}, | ||
}, | ||
LoginType: database.LoginTypePassword, | ||
LoginType: database.LoginTypePassword, | ||
// There's no reason to create the first user as dormant, since you have | ||
coadler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// to login immediately anyways. | ||
accountCreatorName: "coder", | ||
}) | ||
if err != nil { | ||
|
@@ -1343,6 +1347,10 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create | |
err := store.InTx(func(tx database.Store) error { | ||
orgRoles := make([]string, 0) | ||
|
||
status := "" | ||
if req.UserStatus != nil { | ||
status = string(*req.UserStatus) | ||
} | ||
Comment on lines
+1350
to
+1353
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similarly when would we expect the status to be empty? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty just means use the default behavior, which is |
||
params := database.InsertUserParams{ | ||
ID: uuid.New(), | ||
Email: req.Email, | ||
|
@@ -1354,6 +1362,7 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create | |
// All new users are defaulted to members of the site. | ||
RBACRoles: []string{}, | ||
LoginType: req.LoginType, | ||
Status: status, | ||
} | ||
// If a user signs up with OAuth, they can have no password! | ||
if req.Password != "" { | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we pass
""
here? Should we just passactive
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I would omit it but the linter doesn't like leaving it out. It doesn't really matter what this is created as, so
""
just does the default behavior which is dormant.