fix(admin): attribute impersonated container switches to the operator
- handleSwitchUser logged the raw session username, so a container switch
made under an operator ("log in as user") session was recorded as the
user's own action; use auditActor to record "000+<user>"
- localize the session.switch.<target> and color.update audit actions,
which rendered as raw action strings on the audit page
- add a regression test for the impersonated switch actor
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
1 parent
2e2022fd1f
commit
0c159ae504
3 files changed
+46
-2
No files matched your search
@@ -80,6 +80,7 @@ var ACT={
|
||||
'passwd.reset':{zh:'重置面板密码',en:'reset panel password'},
|
||||
'admin.passwd':{zh:'修改管理员密码',en:'change admin password'},
|
||||
'quota.update':{zh:'修改配额',en:'update quota'},
|
||||
'color.update':{zh:'修改用户标识色',en:'update user color'},
|
||||
'blocked.update':{zh:'修改封禁域名列表',en:'update blocked domains'},
|
||||
'snapshot.create':{zh:'创建检查点',en:'create checkpoint'},
|
||||
'snapshot.delete':{zh:'删除检查点',en:'delete checkpoint'},
|
||||
@@ -93,7 +94,13 @@ var ACT={
|
||||
'ipv6pool.del':{zh:'移除 IPv6 地址',en:'remove IPv6 address'},
|
||||
'bandwidth.reset':{zh:'重置流量统计',en:'reset bandwidth'}
|
||||
};
|
||||
function actLabel(a){var m=ACT[a]||{};return LANG==='zh'?(m.zh||a):(m.en||a);}
|
||||
function actLabel(a){
|
||||
if(a.indexOf('session.switch.')===0){
|
||||
var t=a.slice('session.switch.'.length);
|
||||
return LANG==='zh'?('切换机器 → '+t):('switch container → '+t);
|
||||
}
|
||||
var m=ACT[a]||{};return LANG==='zh'?(m.zh||a):(m.en||a);
|
||||
}
|
||||
function pad2(n){return (n<10?'0':'')+n;}
|
||||
function fmtTime(s){
|
||||
if(!s) return '';
|
||||
|
||||
@@ -231,7 +231,7 @@ func (s *Server) handleSwitchUser(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
s.setSessionCookie(w, sess.Token)
|
||||
_ = s.db.AddAuditLog(current.Name, "session.switch."+target.Name)
|
||||
_ = s.db.AddAuditLog(s.auditActor(r, current.Name), "session.switch."+target.Name)
|
||||
http.Redirect(w, r, s.p("/"), http.StatusFound)
|
||||
}
|
||||
|
||||
|
||||
@@ -1798,6 +1798,43 @@ func TestImpersonatedSessionAuditActor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestImpersonatedSwitchAuditActor verifies that switching containers inside a
|
||||
// multi-container group under an operator-created session is attributed to the
|
||||
// operator "000+<user>", not recorded as a plain user action.
|
||||
func TestImpersonatedSwitchAuditActor(t *testing.T) {
|
||||
srv, d := newTestServer(t)
|
||||
h := srv.Handler()
|
||||
prefix := "/" + testSecret
|
||||
hash, _ := pw.Hash("pw")
|
||||
u, err := d.CreateUser("alice", hash, "10.42.0.2", 1, 30001, 10000, 1, 1024, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.CreateUser("alice-1", hash, "10.42.0.3", 2, 30002, 10100, 2, 2048, 20); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sess, err := d.CreateImpersonatedSession(u.ID, 3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cookie := &http.Cookie{Name: "vpsmgr_session", Value: sess.Token, Path: prefix}
|
||||
|
||||
rr := doReq(t, h, http.MethodPost, prefix+"/switch-user", url.Values{"name": {"alice-1"}}, cookie)
|
||||
if rr.Code != http.StatusFound {
|
||||
t.Fatalf("switch-user = %d, want 302", rr.Code)
|
||||
}
|
||||
rows, _ := d.ListAuditLog(0, 10)
|
||||
var found bool
|
||||
for _, r := range rows {
|
||||
if r.Action == "session.switch.alice-1" && r.Actor == "000+alice" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("audit rows = %+v, want session.switch.alice-1 attributed 000+alice", rows)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOverviewImpersonationBanner verifies the "logged in as" banner renders
|
||||
// only when the session is impersonated, with a link back to the admin panel.
|
||||
func TestOverviewImpersonationBanner(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user