mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
fix(controller): enforce ownership on notification group, NAT server, and batch move
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -194,8 +194,6 @@ func validateRule(c *gin.Context, r *model.AlertRule) error {
|
|||||||
return singleton.Localizer.ErrorT("need to configure at least a single rule")
|
return singleton.Localizer.ErrorT("need to configure at least a single rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger task IDs are user-controlled; validate them here so alerts cannot
|
|
||||||
// reference another user's cron and later execute it from the sentinel path.
|
|
||||||
if !singleton.CronShared.CheckPermission(c, slices.Values(r.FailTriggerTasks)) {
|
if !singleton.CronShared.CheckPermission(c, slices.Values(r.FailTriggerTasks)) {
|
||||||
return singleton.Localizer.ErrorT("permission denied")
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
@@ -203,5 +201,9 @@ func validateRule(c *gin.Context, r *model.AlertRule) error {
|
|||||||
return singleton.Localizer.ErrorT("permission denied")
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := assertOwnsNotificationGroup(c, r.NotificationGroupID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ func createCron(c *gin.Context) (uint64, error) {
|
|||||||
return 0, singleton.Localizer.ErrorT("permission denied")
|
return 0, singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := assertOwnsNotificationGroup(c, cf.NotificationGroupID); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
cr.UserID = getUid(c)
|
cr.UserID = getUid(c)
|
||||||
cr.TaskType = cf.TaskType
|
cr.TaskType = cf.TaskType
|
||||||
cr.Name = cf.Name
|
cr.Name = cf.Name
|
||||||
@@ -68,7 +72,6 @@ func createCron(c *gin.Context) (uint64, error) {
|
|||||||
return 0, singleton.Localizer.ErrorT("scheduled tasks cannot be triggered by alarms")
|
return 0, singleton.Localizer.ErrorT("scheduled tasks cannot be triggered by alarms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对于计划任务类型,需要更新CronJob
|
|
||||||
var err error
|
var err error
|
||||||
if cf.TaskType == model.CronTypeCronTask {
|
if cf.TaskType == model.CronTypeCronTask {
|
||||||
if cr.CronJobID, err = singleton.CronShared.AddFunc(cr.Scheduler, singleton.CronTrigger(&cr)); err != nil {
|
if cr.CronJobID, err = singleton.CronShared.AddFunc(cr.Scheduler, singleton.CronTrigger(&cr)); err != nil {
|
||||||
@@ -112,6 +115,10 @@ func updateCron(c *gin.Context) (any, error) {
|
|||||||
return 0, singleton.Localizer.ErrorT("permission denied")
|
return 0, singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := assertOwnsNotificationGroup(c, cf.NotificationGroupID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var cr model.Cron
|
var cr model.Cron
|
||||||
if err := singleton.DB.First(&cr, id).Error; err != nil {
|
if err := singleton.DB.First(&cr, id).Error; err != nil {
|
||||||
return nil, singleton.Localizer.ErrorT("task id %d does not exist", id)
|
return nil, singleton.Localizer.ErrorT("task id %d does not exist", id)
|
||||||
|
|||||||
@@ -53,11 +53,16 @@ func createNAT(c *gin.Context) (uint64, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if server, ok := singleton.ServerShared.Get(nf.ServerID); ok {
|
if nf.ServerID == 0 {
|
||||||
|
return 0, singleton.Localizer.ErrorT("have invalid server id")
|
||||||
|
}
|
||||||
|
server, ok := singleton.ServerShared.Get(nf.ServerID)
|
||||||
|
if !ok {
|
||||||
|
return 0, singleton.Localizer.ErrorT("have invalid server id")
|
||||||
|
}
|
||||||
if !server.HasPermission(c) {
|
if !server.HasPermission(c) {
|
||||||
return 0, singleton.Localizer.ErrorT("permission denied")
|
return 0, singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
uid := getUid(c)
|
uid := getUid(c)
|
||||||
|
|
||||||
@@ -101,11 +106,16 @@ func updateNAT(c *gin.Context) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if server, ok := singleton.ServerShared.Get(nf.ServerID); ok {
|
if nf.ServerID == 0 {
|
||||||
|
return nil, singleton.Localizer.ErrorT("have invalid server id")
|
||||||
|
}
|
||||||
|
server, ok := singleton.ServerShared.Get(nf.ServerID)
|
||||||
|
if !ok {
|
||||||
|
return nil, singleton.Localizer.ErrorT("have invalid server id")
|
||||||
|
}
|
||||||
if !server.HasPermission(c) {
|
if !server.HasPermission(c) {
|
||||||
return nil, singleton.Localizer.ErrorT("permission denied")
|
return nil, singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var n model.NAT
|
var n model.NAT
|
||||||
if err = singleton.DB.First(&n, id).Error; err != nil {
|
if err = singleton.DB.First(&n, id).Error; err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/nezhahq/nezha/model"
|
||||||
|
"github.com/nezhahq/nezha/service/singleton"
|
||||||
|
)
|
||||||
|
|
||||||
|
func callerIsAdmin(c *gin.Context) bool {
|
||||||
|
auth, ok := c.Get(model.CtxKeyAuthorizedUser)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
user, ok := auth.(*model.User)
|
||||||
|
if !ok || user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return user.Role.IsAdmin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCanViewServer(c *gin.Context, server *model.Server) bool {
|
||||||
|
if server == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if callerIsAdmin(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, isMember := c.Get(model.CtxKeyAuthorizedUser); isMember {
|
||||||
|
if server.HasPermission(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return !server.HideForGuest
|
||||||
|
}
|
||||||
|
return !server.HideForGuest
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertOwnsNotificationGroup(c *gin.Context, groupID uint64) error {
|
||||||
|
if groupID == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ng model.NotificationGroup
|
||||||
|
if err := singleton.DB.First(&ng, groupID).Error; err != nil {
|
||||||
|
return singleton.Localizer.ErrorT("notification group id %d does not exist", groupID)
|
||||||
|
}
|
||||||
|
if !ng.HasPermission(c) {
|
||||||
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -336,6 +336,10 @@ func batchMoveServer(c *gin.Context) (any, error) {
|
|||||||
return nil, singleton.Localizer.ErrorT("user id is required")
|
return nil, singleton.Localizer.ErrorT("user id is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !callerIsAdmin(c) && moveForm.ToUser != getUid(c) {
|
||||||
|
return nil, singleton.Localizer.ErrorT("permission denied")
|
||||||
|
}
|
||||||
|
|
||||||
singleton.UserLock.RLock()
|
singleton.UserLock.RLock()
|
||||||
defer singleton.UserLock.RUnlock()
|
defer singleton.UserLock.RUnlock()
|
||||||
if _, ok := singleton.UserInfoMap[moveForm.ToUser]; !ok {
|
if _, ok := singleton.UserInfoMap[moveForm.ToUser]; !ok {
|
||||||
@@ -412,10 +416,10 @@ func getServerMetrics(c *gin.Context) (*model.ServerMetricsResponse, error) {
|
|||||||
return nil, singleton.Localizer.ErrorT("server not found")
|
return nil, singleton.Localizer.ErrorT("server not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
if !userCanViewServer(c, server) {
|
||||||
if server.HideForGuest && !isMember {
|
|
||||||
return nil, singleton.Localizer.ErrorT("unauthorized")
|
return nil, singleton.Localizer.ErrorT("unauthorized")
|
||||||
}
|
}
|
||||||
|
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||||
|
|
||||||
metricName := c.Query("metric")
|
metricName := c.Query("metric")
|
||||||
metricType, ok := serverMetricMap[metricName]
|
metricType, ok := serverMetricMap[metricName]
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func getServiceHistory(c *gin.Context) (*model.ServiceHistoryResponse, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !singleton.TSDBEnabled() {
|
if !singleton.TSDBEnabled() {
|
||||||
return queryServiceHistoryFromDB(serviceID, period, response)
|
return queryServiceHistoryFromDB(c, serviceID, period, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := singleton.TSDBShared.QueryServiceHistory(serviceID, period)
|
result, err := singleton.TSDBShared.QueryServiceHistory(serviceID, period)
|
||||||
@@ -120,17 +120,21 @@ func getServiceHistory(c *gin.Context) (*model.ServiceHistoryResponse, error) {
|
|||||||
|
|
||||||
serverMap := singleton.ServerShared.GetList()
|
serverMap := singleton.ServerShared.GetList()
|
||||||
|
|
||||||
|
filtered := result.Servers[:0]
|
||||||
for i := range result.Servers {
|
for i := range result.Servers {
|
||||||
if server, ok := serverMap[result.Servers[i].ServerID]; ok {
|
server, ok := serverMap[result.Servers[i].ServerID]
|
||||||
|
if !ok || !userCanViewServer(c, server) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
result.Servers[i].ServerName = server.Name
|
result.Servers[i].ServerName = server.Name
|
||||||
|
filtered = append(filtered, result.Servers[i])
|
||||||
}
|
}
|
||||||
}
|
response.Servers = filtered
|
||||||
response.Servers = result.Servers
|
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func queryServiceHistoryFromDB(serviceID uint64, period tsdb.QueryPeriod, response *model.ServiceHistoryResponse) (*model.ServiceHistoryResponse, error) {
|
func queryServiceHistoryFromDB(c *gin.Context, serviceID uint64, period tsdb.QueryPeriod, response *model.ServiceHistoryResponse) (*model.ServiceHistoryResponse, error) {
|
||||||
since := time.Now().Add(-period.Duration())
|
since := time.Now().Add(-period.Duration())
|
||||||
|
|
||||||
var histories []model.ServiceHistory
|
var histories []model.ServiceHistory
|
||||||
@@ -146,11 +150,13 @@ func queryServiceHistoryFromDB(serviceID uint64, period tsdb.QueryPeriod, respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
for serverID, records := range grouped {
|
for serverID, records := range grouped {
|
||||||
|
server, ok := serverMap[serverID]
|
||||||
|
if !ok || !userCanViewServer(c, server) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
stats := model.ServerServiceStats{
|
stats := model.ServerServiceStats{
|
||||||
ServerID: serverID,
|
ServerID: serverID,
|
||||||
}
|
ServerName: server.Name,
|
||||||
if server, ok := serverMap[serverID]; ok {
|
|
||||||
stats.ServerName = server.Name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalDelay float64
|
var totalDelay float64
|
||||||
@@ -216,12 +222,10 @@ func listServerServices(c *gin.Context) ([]*model.ServiceInfos, error) {
|
|||||||
return nil, singleton.Localizer.ErrorT("server not found")
|
return nil, singleton.Localizer.ErrorT("server not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
if !userCanViewServer(c, server) {
|
||||||
authorized := isMember
|
|
||||||
|
|
||||||
if server.HideForGuest && !authorized {
|
|
||||||
return nil, singleton.Localizer.ErrorT("unauthorized")
|
return nil, singleton.Localizer.ErrorT("unauthorized")
|
||||||
}
|
}
|
||||||
|
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
||||||
|
|
||||||
// 解析时间范围
|
// 解析时间范围
|
||||||
periodStr := c.DefaultQuery("period", "1d")
|
periodStr := c.DefaultQuery("period", "1d")
|
||||||
@@ -373,16 +377,13 @@ func listServerWithServices(c *gin.Context) ([]uint64, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
|
|
||||||
authorized := isMember
|
|
||||||
|
|
||||||
var ret []uint64
|
var ret []uint64
|
||||||
for id := range serverIDSet {
|
for id := range serverIDSet {
|
||||||
server, ok := serverMap[id]
|
server, ok := serverMap[id]
|
||||||
if !ok || server == nil {
|
if !ok || server == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !server.HideForGuest || authorized {
|
if userCanViewServer(c, server) {
|
||||||
ret = append(ret, id)
|
ret = append(ret, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,8 +546,6 @@ func validateServers(c *gin.Context, ss *model.Service) error {
|
|||||||
return singleton.Localizer.ErrorT("permission denied")
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger task IDs are user-controlled; validate them here so services cannot
|
|
||||||
// reference another user's cron and later execute it from the sentinel path.
|
|
||||||
if !singleton.CronShared.CheckPermission(c, slices.Values(ss.FailTriggerTasks)) {
|
if !singleton.CronShared.CheckPermission(c, slices.Values(ss.FailTriggerTasks)) {
|
||||||
return singleton.Localizer.ErrorT("permission denied")
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
@@ -554,5 +553,9 @@ func validateServers(c *gin.Context, ss *model.Service) error {
|
|||||||
return singleton.Localizer.ErrorT("permission denied")
|
return singleton.Localizer.ErrorT("permission denied")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := assertOwnsNotificationGroup(c, ss.NotificationGroupID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user