diff --git a/migrations/migrate.go b/migrations/migrate.go index 37bffbc..2834af9 100755 --- a/migrations/migrate.go +++ b/migrations/migrate.go @@ -238,14 +238,15 @@ func SeedStatuspage() { insertados = append(insertados, sub) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Statuspage completado.") } @@ -582,14 +583,15 @@ func SeedIntegraciones() { } // 3. Asignar a todos los roles existentes - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Integraciones completado.") } @@ -632,14 +634,15 @@ func SeedPasarelas() { insertados = append(insertados, sub) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Pasarelas completado.") } @@ -705,14 +708,15 @@ func SeedServidores() { } // 3. Asignar a todos los roles existentes - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Servidores completado.") } @@ -772,14 +776,15 @@ func SeedAdministracion() { insertados = append(insertados, sub) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Administración completado.") } @@ -823,14 +828,15 @@ func SeedSaas() { insertados = append(insertados, sub) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { - log.Printf("[SEED] Error obteniendo roles: %v", err) + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de SaaS completado.") } @@ -896,13 +902,15 @@ func SeedTelegram() { log.Printf("[SEED] Submódulo 'Telegram' ya existe (ID %d)", sub.ID) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub}) - } + asignarSubmodulosSiFaltan(&rolAdmin, []models.Submodules{sub}) log.Println("[SEED] Seed de Telegram completado.") } @@ -954,13 +962,15 @@ func SeedPortalClientes() { insertados = append(insertados, sub) } - var roles []models.Roles - if err := db.Find(&roles).Error; err != nil { + // Solo se asigna al rol Administrador: los demás roles pueden estar + // restringidos a propósito y no deben recibir módulos nuevos por su cuenta + // — hay que habilitarlos manualmente desde /app/roles si corresponde. + var rolAdmin models.Roles + if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { + log.Printf("[SEED] Rol 'Administrador' no encontrado: %v", err) return } - for _, rol := range roles { - asignarSubmodulosSiFaltan(&rol, insertados) - } + asignarSubmodulosSiFaltan(&rolAdmin, insertados) log.Println("[SEED] Seed de Portal de Clientes completado.") } @@ -1134,7 +1144,7 @@ func SeedWebSms() { if err := db.Where("name = ?", "Administrador").First(&rolAdmin).Error; err != nil { log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err) } else { - db.Model(&rolAdmin).Association("Submodules").Append(&[]models.Submodules{sub}) + asignarSubmodulosSiFaltan(&rolAdmin, []models.Submodules{sub}) log.Printf("[SEED] Submódulo 'WebSMS' asignado al rol 'Administrador'") } log.Println("[SEED] Seed de WebSMS completado.") @@ -1170,7 +1180,7 @@ func SeedUrlMonitor() { if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil { log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err) } else { - db.Model(&rol).Association("Submodules").Append(&[]models.Submodules{sub}) + asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub}) log.Printf("[SEED] Submódulo 'Monitor de URLs' asignado al rol 'Administrador'") } log.Println("[SEED] Seed de Monitor de URLs completado.") @@ -1206,7 +1216,7 @@ func SeedTareas() { if err := db.Where("name = ?", "Administrador").First(&rol).Error; err != nil { log.Printf("[SEED] Rol 'Administrador' no encontrado, se omite asignación: %v", err) } else { - db.Model(&rol).Association("Submodules").Append(&[]models.Submodules{sub}) + asignarSubmodulosSiFaltan(&rol, []models.Submodules{sub}) log.Printf("[SEED] Submódulo 'Tareas' asignado al rol 'Administrador'") } log.Println("[SEED] Seed de Tareas completado.") diff --git a/rest/controllers/query_runner_controller.go b/rest/controllers/query_runner_controller.go index bfc8700..86df2c4 100644 --- a/rest/controllers/query_runner_controller.go +++ b/rest/controllers/query_runner_controller.go @@ -98,6 +98,31 @@ func GetTables(c *fiber.Ctx) error { return c.JSON(fiber.Map{"data": tables}) } +// usuarioPuedeUsarConx verifica que el usuario autenticado pueda ejecutar SQL +// contra esa conexión: admin ve todas, el resto solo las que su rol tiene +// asignadas en Role.ConxDBs (el mismo criterio que ya usa GetConxDbList para +// decidir qué mostrar en el selector — aquí se hace cumplir de verdad). +// +// Sin este chequeo, cualquiera con acceso al submódulo Query Runner podía +// mandar cualquier conx_db_id por API y ejecutar SQL contra una conexión que +// nunca se le mostró ni se le asignó a su rol. +func usuarioPuedeUsarConx(c *fiber.Ctx, conxDbID uint) bool { + user, err := auth.User(c) + if err != nil || user == nil { + // Llamada sin sesión (Hermes/API con su propia auth): no restringido aquí. + return true + } + if user.IsAdmin { + return true + } + for _, db := range user.Role.ConxDBs { + if db.ID == conxDbID { + return true + } + } + return false +} + // RunQuery ejecuta una consulta SQL. // POST /app/query-runner/run // Body: { conx_db_id, database, sql } @@ -113,6 +138,9 @@ func RunQuery(c *fiber.Ctx) error { if strings.TrimSpace(body.SQL) == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "La consulta está vacía"}) } + if !usuarioPuedeUsarConx(c, body.ConxDbID) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "No tienes acceso a esta conexión"}) + } conx, err := loadConxDb(strconv.Itoa(int(body.ConxDbID))) if err != nil { @@ -179,6 +207,12 @@ func RunBatchQuery(c *fiber.Ctx) error { return c.Status(400).JSON(fiber.Map{"error": "No hay consultas para ejecutar. Envía sqls[] o un archivo .sql"}) } + if conxIDNum, parseErr := strconv.ParseUint(conxDbIDStr, 10, 32); parseErr == nil { + if !usuarioPuedeUsarConx(c, uint(conxIDNum)) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "No tienes acceso a esta conexión"}) + } + } + conx, err := loadConxDb(conxDbIDStr) if err != nil { return c.Status(400).JSON(fiber.Map{"error": err.Error()}) @@ -599,13 +633,13 @@ func heuristicSQLFix(sql string) string { "ORDR": "ORDER", "ORDRE": "ORDER", "ORBER": "ORDER", "HAVNG": "HAVING", "HAVIN": "HAVING", "HVING": "HAVING", "LIMT": "LIMIT", "LIIMT": "LIMIT", - "JON": "JOIN", "JOUN": "JOIN", + "JON": "JOIN", "JOUN": "JOIN", "LEF JOIN": "LEFT JOIN", "LEFTJ OIN": "LEFT JOIN", "RIGTH": "RIGHT", "CRATE": "CREATE", "CREARE": "CREATE", "TABEL": "TABLE", "TBALE": "TABLE", "ALTR": "ALTER", "ALTE": "ALTER", - "DRO": "DROP", "DROPP": "DROP", + "DRO": "DROP", "DROPP": "DROP", "IDNEX": "INDEX", "INEX": "INDEX", "PRIMRY": "PRIMARY", "PRIMAR": "PRIMARY", "PRMARY": "PRIMARY", "FORIGN": "FOREIGN", "FOREIN": "FOREIGN", "FORIEGN": "FOREIGN", diff --git a/rest/routes/user.go b/rest/routes/user.go index 1ec7426..04c05c7 100755 --- a/rest/routes/user.go +++ b/rest/routes/user.go @@ -141,8 +141,12 @@ func UserRoutes(app fiber.Router) { protected.Get("/query-runner/databases", controllers.GetDatabases) protected.Get("/query-runner/tables", controllers.GetTables) protected.Get("/query-runner/test", controllers.TestConnection) - protected.Post("/query-runner/run", middlewares.SoloAdmin, controllers.RunQuery) - protected.Post("/query-runner/run-batch", middlewares.SoloAdmin, controllers.RunBatchQuery) + // Sin SoloAdmin a propósito: RunQuery/RunBatchQuery ya limitan el alcance + // internamente (un no-admin solo ve/consulta las conexiones que su rol + // tiene asignadas vía Role.ConxDBs). El acceso real se controla asignando o + // no el submódulo "Query Runner" a su rol, no aquí. + protected.Post("/query-runner/run", controllers.RunQuery) + protected.Post("/query-runner/run-batch", controllers.RunBatchQuery) protected.Get("/query-runner/history", controllers.GetHistory) protected.Delete("/query-runner/history", controllers.ClearHistory) protected.Post("/query-runner/export/csv", controllers.ExportCSV)