Auth: Improve privilege level change detection #3512

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-07-18 23:35:10 +02:00
parent 08070978cf
commit 4931889b5e
5 changed files with 61 additions and 13 deletions

View file

@ -36,11 +36,11 @@ func UploadUserAvatar(router *gin.RouterGroup) {
} }
// Check if the session user is has user management privileges. // Check if the session user is has user management privileges.
isPrivileged := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
uid := clean.UID(c.Param("uid")) uid := clean.UID(c.Param("uid"))
// Users may only change their own avatar. // Users may only change their own avatar.
if !isPrivileged && s.User().UserUID != uid { if !isAdmin && s.User().UserUID != uid {
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "user does not match"}, s.RefID) event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "user does not match"}, s.RefID)
AbortForbidden(c) AbortForbidden(c)
return return

View file

@ -43,19 +43,19 @@ func UpdateUserPassword(router *gin.RouterGroup) {
} }
// Check if the session user is has user management privileges. // Check if the session user is has user management privileges.
isPrivileged := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
isSuperAdmin := isPrivileged && s.User().IsSuperAdmin() isSuperAdmin := isAdmin && s.User().IsSuperAdmin()
uid := clean.UID(c.Param("uid")) uid := clean.UID(c.Param("uid"))
var u *entity.User var u *entity.User
// Users may only change their own password. // Users may only change their own password.
if !isPrivileged && s.User().UserUID != uid { if !isAdmin && s.User().UserUID != uid {
AbortForbidden(c) AbortForbidden(c)
return return
} else if s.User().UserUID == uid { } else if s.User().UserUID == uid {
u = s.User() u = s.User()
isPrivileged = false isAdmin = false
isSuperAdmin = false isSuperAdmin = false
} else if u = entity.FindUserByUID(uid); u == nil { } else if u = entity.FindUserByUID(uid); u == nil {
Abort(c, http.StatusNotFound, i18n.ErrUserNotFound) Abort(c, http.StatusNotFound, i18n.ErrUserNotFound)

View file

@ -61,7 +61,8 @@ func UpdateUser(router *gin.RouterGroup) {
} }
// Check if the session user is has user management privileges. // Check if the session user is has user management privileges.
isPrivileged := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage}) isAdmin := acl.Resources.AllowAll(acl.ResourceUsers, s.User().AclRole(), acl.Permissions{acl.AccessAll, acl.ActionManage})
privilegeLevelChange := isAdmin && m.PrivilegeLevelChange(f)
// Prevent super admins from locking themselves out. // Prevent super admins from locking themselves out.
if u := s.User(); u.IsSuperAdmin() && u.Equal(m) && !f.CanLogin { if u := s.User(); u.IsSuperAdmin() && u.Equal(m) && !f.CanLogin {
@ -69,7 +70,7 @@ func UpdateUser(router *gin.RouterGroup) {
} }
// Save model with values from form. // Save model with values from form.
if err = m.SaveForm(f, isPrivileged); err != nil { if err = m.SaveForm(f, isAdmin); err != nil {
event.AuditErr([]string{ClientIP(c), "session %s", "users", m.UserName, "update", err.Error()}, s.RefID) event.AuditErr([]string{ClientIP(c), "session %s", "users", m.UserName, "update", err.Error()}, s.RefID)
AbortSaveFailed(c) AbortSaveFailed(c)
return return
@ -78,9 +79,9 @@ func UpdateUser(router *gin.RouterGroup) {
// Log event. // Log event.
event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "updated"}, s.RefID) event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "updated"}, s.RefID)
// Delete user sessions after a permission level change. // Delete user sessions after a privilege level change.
// see https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#renew-the-session-id-after-any-privilege-level-change // see https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#renew-the-session-id-after-any-privilege-level-change
if isPrivileged { if privilegeLevelChange {
// Prevent the current session from being deleted. // Prevent the current session from being deleted.
deleted := m.DeleteSessions([]string{s.ID}) deleted := m.DeleteSessions([]string{s.ID})
event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "invalidated %s"}, s.RefID, event.AuditInfo([]string{ClientIP(c), "session %s", "users", m.UserName, "invalidated %s"}, s.RefID,

View file

@ -1016,8 +1016,20 @@ func (m *User) Form() (form.User, error) {
return frm, nil return frm, nil
} }
// PrivilegeLevelChange checks if saving the form changes the user privileges.
func (m *User) PrivilegeLevelChange(f form.User) bool {
return m.UserRole != f.Role() ||
m.SuperAdmin != f.SuperAdmin ||
m.CanLogin != f.CanLogin ||
m.WebDAV != f.WebDAV ||
m.UserAttr != f.Attr() ||
m.AuthProvider != f.Provider().String() ||
m.BasePath != f.BasePath ||
m.UploadPath != f.UploadPath
}
// SaveForm updates the entity using form data and stores it in the database. // SaveForm updates the entity using form data and stores it in the database.
func (m *User) SaveForm(f form.User, updateRights bool) error { func (m *User) SaveForm(f form.User, changePrivileges bool) error {
if m.UserName == "" || m.ID <= 0 { if m.UserName == "" || m.ID <= 0 {
return fmt.Errorf("system users cannot be modified") return fmt.Errorf("system users cannot be modified")
} else if (m.ID == 1 || f.SuperAdmin) && acl.RoleAdmin.NotEqual(f.Role()) { } else if (m.ID == 1 || f.SuperAdmin) && acl.RoleAdmin.NotEqual(f.Role()) {
@ -1055,8 +1067,8 @@ func (m *User) SaveForm(f form.User, updateRights bool) error {
m.VerifyToken = GenerateToken() m.VerifyToken = GenerateToken()
} }
// Update user rights only if explicitly requested. // Change user privileges only if allowed.
if updateRights { if changePrivileges {
m.SetRole(f.Role()) m.SetRole(f.Role())
m.SuperAdmin = f.SuperAdmin m.SuperAdmin = f.SuperAdmin

View file

@ -920,6 +920,41 @@ func TestUser_Form(t *testing.T) {
}) })
} }
func TestUser_PrivilegeLevelChange(t *testing.T) {
t.Run("True", func(t *testing.T) {
m := FindUserByName("alice")
if m == nil {
t.Fatal("result should not be nil")
}
frm, err := m.Form()
if err != nil {
t.Fatal(err)
}
frm.UserRole = "guest"
assert.True(t, m.PrivilegeLevelChange(frm))
})
t.Run("False", func(t *testing.T) {
m := FindUserByName("alice")
if m == nil {
t.Fatal("result should not be nil")
}
frm, err := m.Form()
if err != nil {
t.Fatal(err)
}
assert.False(t, m.PrivilegeLevelChange(frm))
})
}
func TestUser_SaveForm(t *testing.T) { func TestUser_SaveForm(t *testing.T) {
t.Run("UnknownUser", func(t *testing.T) { t.Run("UnknownUser", func(t *testing.T) {
frm, err := UnknownUser.Form() frm, err := UnknownUser.Form()