Auth: Improve privilege level change detection #3512
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
08070978cf
commit
4931889b5e
5 changed files with 61 additions and 13 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue