修复漏洞,重构初始化功能,优化媒体库 (#1024)

* 媒体库增加 普通上传、压缩上传按钮,方便媒体库直接上传图片

* 增加数据类型切换后的的校验,避免使用错误的查询条件和字典条件。

* refactor: 重构初始化逻辑

* 媒体库功能丰富

* 修复注入漏洞和路径穿越

* 修复自动化接口获取数据库表失败后未能终止的bug

* 微调媒体库样式

Co-authored-by: bypanghu <bypanghu@163.com>
Co-authored-by: tesun <36953434+tesun@users.noreply.github.com>
Co-authored-by: pnck <hio131@gmail.com>
Co-authored-by: task <121913992@qq.com>
This commit is contained in:
奇淼(piexlmax
2022-04-12 17:57:11 +08:00
committed by GitHub
co-authored by bypanghu tesun pnck task
parent fe539baa34
commit 6fb6ac2d6c
40 changed files with 1366 additions and 792 deletions
+2 -2
View File
@@ -6,8 +6,8 @@ import (
)
type ServiceGroup struct {
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
SystemServiceGroup system.ServiceGroup
ExampleServiceGroup example.ServiceGroup
}
var ServiceGroupApp = new(ServiceGroup)
@@ -53,6 +53,12 @@ func (e *FileUploadAndDownloadService) DeleteFile(file example.ExaFileUploadAndD
return err
}
// EditFileName 编辑文件名或者备注
func (e *FileUploadAndDownloadService) EditFileName(file example.ExaFileUploadAndDownload) (err error) {
var fileFromDb example.ExaFileUploadAndDownload
return global.GVA_DB.Where("id = ?", file.ID).First(&fileFromDb).Update("name", file.Name).Error
}
//@author: [piexlmax](https://github.com/piexlmax)
//@function: GetFileRecordInfoList
//@description: 分页获取数据
@@ -62,8 +68,12 @@ func (e *FileUploadAndDownloadService) DeleteFile(file example.ExaFileUploadAndD
func (e *FileUploadAndDownloadService) GetFileRecordInfoList(info request.PageInfo) (err error, list interface{}, total int64) {
limit := info.PageSize
offset := info.PageSize * (info.Page - 1)
keyword := info.Keyword
db := global.GVA_DB.Model(&example.ExaFileUploadAndDownload{})
var fileLists []example.ExaFileUploadAndDownload
if len(keyword) > 0 {
db = db.Where("name LIKE ?", "%"+keyword+"%")
}
err = db.Count(&total).Error
if err != nil {
return
+5 -7
View File
@@ -1,8 +1,6 @@
package system
import (
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
"github.com/pkg/errors"
@@ -74,17 +72,17 @@ func (a *autoCodePgsql) GetColumn(tableName string, dbName string) (data []respo
where attribute.attrelid =
(select oid from pg_class class where class.relname = '@table_name') and attname =columns.COLUMN_NAME )) as column_comment
FROM INFORMATION_SCHEMA.COLUMNS columns
WHERE table_catalog = '@table_catalog'
WHERE table_catalog = '?'
and table_schema = 'public'
and table_name = '@table_name';
and table_name = '?';
`
var entities []response.Column
db, _err := gorm.Open(postgres.Open(global.GVA_CONFIG.Pgsql.LinkDsn(dbName)), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)})
if _err != nil {
return nil, errors.Wrapf(err, "[pgsql] 连接 数据库(%s)的表(%s)失败!", dbName, tableName)
}
sql = strings.ReplaceAll(sql, "@table_catalog", dbName)
sql = strings.ReplaceAll(sql, "@table_name", tableName)
err = db.Raw(sql).Scan(&entities).Error
//sql = strings.ReplaceAll(sql, "@table_catalog", dbName)
//sql = strings.ReplaceAll(sql, "@table_name", tableName)
err = db.Raw(sql, dbName, tableName).Scan(&entities).Error
return entities, err
}
+146 -43
View File
@@ -1,65 +1,136 @@
package system
import (
"context"
"database/sql"
"errors"
"fmt"
adapter "github.com/casbin/gorm-adapter/v3"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/example"
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"gorm.io/gorm"
"sort"
)
const (
Mysql = "mysql"
Pgsql = "pgsql"
InitSuccess = "\n[%v] --> 初始数据成功!\n"
InitDataExist = "\n[%v] --> %v 的初始数据已存在!\n"
InitDataFailed = "\n[%v] --> %v 初始数据失败! \nerr: %+v\n"
InitDataSuccess = "\n[%v] --> %v 初始数据成功!\n"
)
const (
InitOrderSystem = 10
InitOrderInternal = 1000
InitOrderExternal = 100000
)
var (
ErrMissingDBContext = errors.New("missing db in context")
ErrMissingDependentContext = errors.New("missing dependent value in context")
ErrDBTypeMismatch = errors.New("db type mismatch")
)
// SubInitializer 提供 source/*/init() 使用的接口,每个 initializer 完成一个初始化过程
type SubInitializer interface {
InitializerName() string // 不一定代表单独一个表,所以改成了更宽泛的语义
MigrateTable(ctx context.Context) (next context.Context, err error)
InitializeData(ctx context.Context) (next context.Context, err error)
TableCreated(ctx context.Context) bool
DataInserted(ctx context.Context) bool
}
// TypedDBInitHandler 执行传入的 initializer
type TypedDBInitHandler interface {
EnsureDB(ctx context.Context, conf *request.InitDB) (context.Context, error) // 建库,失败属于 fatal error,因此让它 panic
WriteConfig(ctx context.Context) error // 回写配置
InitTables(ctx context.Context, inits initSlice) error // 建表 handler
InitData(ctx context.Context, inits initSlice) error // 建数据 handler
}
// orderedInitializer 组合一个顺序字段,以供排序
type orderedInitializer struct {
order int
SubInitializer
}
// initSlice 供 initializer 排序依赖时使用
type initSlice []*orderedInitializer
var (
initializers initSlice
cache map[string]*orderedInitializer
)
// RegisterInit 注册要执行的初始化过程,会在 InitDB() 时调用
func RegisterInit(order int, i SubInitializer) {
if initializers == nil {
initializers = initSlice{}
}
if cache == nil {
cache = map[string]*orderedInitializer{}
}
name := i.InitializerName()
if _, existed := cache[name]; existed {
panic(fmt.Sprintf("Name conflict on %s", name))
}
ni := orderedInitializer{order, i}
initializers = append(initializers, &ni)
cache[name] = &ni
}
/* ---- * service * ---- */
type InitDBService struct{}
// InitDB 创建数据库并初始化 总入口
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (initDBService *InitDBService) InitDB(conf request.InitDB) error {
func (initDBService *InitDBService) InitDB(conf request.InitDB) (err error) {
ctx := context.TODO()
if len(initializers) == 0 {
return errors.New("无可用初始化过程,请检查初始化是否已执行完成")
}
sort.Sort(&initializers) // 保证有依赖的 initializer 排在后面执行
// Note: 若 initializer 只有单一依赖,可以写为 B=A+1, C=A+1; 由于 BC 之间没有依赖关系,所以谁先谁后并不影响初始化
// 若存在多个依赖,可以写为 C=A+B, D=A+B+C, E=A+1;
// C必然>A|B,因此在AB之后执行,D必然>A|B|C,因此在ABC后执行,而E只依赖A,顺序与CD无关,因此E与CD哪个先执行并不影响
var initHandler TypedDBInitHandler
switch conf.DBType {
case "mysql":
return initDBService.initMysqlDB(conf)
initHandler = NewMysqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mysql")
case "pgsql":
return initDBService.initPgsqlDB(conf)
initHandler = NewPgsqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "pgsql")
default:
return initDBService.initMysqlDB(conf)
initHandler = NewMysqlInitHandler()
ctx = context.WithValue(ctx, "dbtype", "mysql")
}
ctx, err = initHandler.EnsureDB(ctx, &conf)
if err != nil {
return err
}
db := ctx.Value("db").(*gorm.DB)
global.GVA_DB = db
if err = initHandler.WriteConfig(ctx); err != nil {
return err
}
if err = initHandler.InitTables(ctx, initializers); err != nil {
return err
}
if err = initHandler.InitData(ctx, initializers); err != nil {
return err
}
initializers = initSlice{}
cache = map[string]*orderedInitializer{}
return nil
}
// initTables 初始化表
// Author [SliverHorn](https://github.com/SliverHorn)
func (initDBService *InitDBService) initTables() error {
return global.GVA_DB.AutoMigrate(
system.SysApi{},
system.SysUser{},
system.SysBaseMenu{},
system.SysAuthority{},
system.JwtBlacklist{},
system.SysDictionary{},
system.SysAutoCodeHistory{},
system.SysOperationRecord{},
system.SysDictionaryDetail{},
system.SysBaseMenuParameter{},
system.SysBaseMenuBtn{},
system.SysAuthorityBtn{},
system.SysAutoCode{},
adapter.CasbinRule{},
example.ExaFile{},
example.ExaCustomer{},
example.ExaFileChunk{},
example.ExaFileUploadAndDownload{},
)
}
// createDatabase 创建数据库(mysql)
// Author [SliverHorn](https://github.com/SliverHorn)
// Author: [songzhibin97](https://github.com/songzhibin97)
func (initDBService *InitDBService) createDatabase(dsn string, driver string, createSql string) error {
// createDatabase 创建数据库( EnsureDB() 中调用 )
func createDatabase(dsn string, driver string, createSql string) error {
db, err := sql.Open(driver, dsn)
if err != nil {
return err
@@ -76,3 +147,35 @@ func (initDBService *InitDBService) createDatabase(dsn string, driver string, cr
_, err = db.Exec(createSql)
return err
}
// createTables 创建表(默认 dbInitHandler.initTables 行为)
func createTables(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.TableCreated(next) {
continue
}
if n, err := init.MigrateTable(next); err != nil {
return err
} else {
next = n
}
}
return nil
}
/* -- sortable interface -- */
func (a initSlice) Len() int {
return len(a)
}
func (a initSlice) Less(i, j int) bool {
return a[i].order < a[j].order
}
func (a initSlice) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
+60 -62
View File
@@ -1,27 +1,36 @@
package system
import (
"context"
"errors"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/gookit/color"
"path/filepath"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/source/example"
"github.com/flipped-aurora/gin-vue-admin/server/source/system"
uuid "github.com/satori/go.uuid"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// writeMysqlConfig mysql回写配置
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [songzhibin97](https://github.com/songzhibin97)
func (initDBService *InitDBService) writeMysqlConfig(mysql config.Mysql) error {
global.GVA_CONFIG.Mysql = mysql
type MysqlInitHandler struct{}
func NewMysqlInitHandler() *MysqlInitHandler {
return &MysqlInitHandler{}
}
// WriteConfig mysql回写配置
func (h MysqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Mysql)
if !ok {
return errors.New("mysql config invalid")
}
global.GVA_CONFIG.System.DbType = "mysql"
global.GVA_CONFIG.Mysql = c
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
@@ -30,66 +39,55 @@ func (initDBService *InitDBService) writeMysqlConfig(mysql config.Mysql) error {
return global.GVA_VP.WriteConfig()
}
// initMysqlDB 创建数据库并初始化 mysql
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
// Author: [songzhibin97](https://github.com/songzhibin97)
func (initDBService *InitDBService) initMysqlDB(conf request.InitDB) error {
// EnsureDB 创建数据库并初始化 mysql
func (h MysqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "mysql" {
return ctx, ErrDBTypeMismatch
}
dsn := conf.MysqlEmptyDsn()
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", conf.DBName)
if err := initDBService.createDatabase(dsn, "mysql", createSql); err != nil {
return err
if err = createDatabase(dsn, "mysql", createSql); err != nil {
return nil, err
} // 创建数据库
mysqlConfig := conf.ToMysqlConfig()
if mysqlConfig.Dbname == "" {
return nil
c := conf.ToMysqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
if db, err := gorm.Open(mysql.New(mysql.Config{
DSN: mysqlConfig.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: true, // 根据版本自动配置
var db *gorm.DB
if db, err = gorm.Open(mysql.New(mysql.Config{
DSN: c.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: true, // 根据版本自动配置
}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return nil
} else {
global.GVA_DB = db
return ctx, err
}
if err := initDBService.initTables(); err != nil {
global.GVA_DB = nil
return err
}
if err := initDBService.initMysqlData(); err != nil {
global.GVA_DB = nil
return err
}
if err := initDBService.writeMysqlConfig(mysqlConfig); err != nil {
return err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h MysqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h MysqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for _, init := range inits {
if init.DataInserted(next) {
color.Info.Printf(InitDataExist, Mysql, init.InitializerName())
continue
}
if n, err := init.InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Mysql, err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Mysql, init.InitializerName())
}
}
color.Info.Printf(InitSuccess, Mysql)
return nil
}
// initData mysql 初始化数据
// Author [SliverHorn](https://github.com/SliverHorn)
// Author: [songzhibin97](https://github.com/songzhibin97)
func (initDBService *InitDBService) initMysqlData() error {
return model.MysqlDataInitialize(
system.Api,
system.User,
system.Casbin,
system.BaseMenu,
system.Authority,
system.Dictionary,
system.UserAuthority,
system.DataAuthorities,
system.AuthoritiesMenus,
system.DictionaryDetail,
system.ViewAuthorityMenuMysql,
example.FileMysql,
)
}
+59 -56
View File
@@ -1,26 +1,36 @@
package system
import (
"context"
"errors"
"fmt"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/gookit/color"
"path/filepath"
"github.com/flipped-aurora/gin-vue-admin/server/utils"
"github.com/flipped-aurora/gin-vue-admin/server/config"
"github.com/flipped-aurora/gin-vue-admin/server/global"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"github.com/flipped-aurora/gin-vue-admin/server/source/example"
"github.com/flipped-aurora/gin-vue-admin/server/source/system"
uuid "github.com/satori/go.uuid"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// writePgsqlConfig pgsql 回写配置
// Author [SliverHorn](https://github.com/SliverHorn)
func (initDBService *InitDBService) writePgsqlConfig(pgsql config.Pgsql) error {
type PgsqlInitHandler struct{}
func NewPgsqlInitHandler() *PgsqlInitHandler {
return &PgsqlInitHandler{}
}
// WriteConfig pgsql 回写配置
func (h PgsqlInitHandler) WriteConfig(ctx context.Context) error {
c, ok := ctx.Value("config").(config.Pgsql)
if !ok {
return errors.New("postgresql config invalid")
}
global.GVA_CONFIG.System.DbType = "pgsql"
global.GVA_CONFIG.Pgsql = pgsql
global.GVA_CONFIG.Pgsql = c
cs := utils.StructToMap(global.GVA_CONFIG)
for k, v := range cs {
global.GVA_VP.Set(k, v)
@@ -29,61 +39,54 @@ func (initDBService *InitDBService) writePgsqlConfig(pgsql config.Pgsql) error {
return global.GVA_VP.WriteConfig()
}
func (initDBService *InitDBService) initPgsqlDB(conf request.InitDB) error {
// EnsureDB 创建数据库并初始化 pg
func (h PgsqlInitHandler) EnsureDB(ctx context.Context, conf *request.InitDB) (next context.Context, err error) {
if s, ok := ctx.Value("dbtype").(string); !ok || s != "pgsql" {
return ctx, ErrDBTypeMismatch
}
dsn := conf.PgsqlEmptyDsn()
createSql := "CREATE DATABASE " + conf.DBName
if err := initDBService.createDatabase(dsn, "pgx", createSql); err != nil {
return err
createSql := fmt.Sprintf("CREATE DATABASE %s;", conf.DBName)
if err = createDatabase(dsn, "pgx", createSql); err != nil {
return nil, err
} // 创建数据库
pgsqlConfig := conf.ToPgsqlConfig()
if pgsqlConfig.Dbname == "" {
return nil
c := conf.ToPgsqlConfig()
next = context.WithValue(ctx, "config", c)
if c.Dbname == "" {
return ctx, nil
} // 如果没有数据库名, 则跳出初始化数据
if db, err := gorm.Open(postgres.New(postgres.Config{
DSN: pgsqlConfig.Dsn(), // DSN data source name
var db *gorm.DB
if db, err = gorm.Open(postgres.New(postgres.Config{
DSN: c.Dsn(), // DSN data source name
PreferSimpleProtocol: false,
}), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}); err != nil {
return nil
} else {
global.GVA_DB = db
return ctx, err
}
if err := initDBService.initTables(); err != nil {
global.GVA_DB = nil
return err
}
if err := initDBService.initPgsqlData(); err != nil {
global.GVA_DB = nil
return err
}
if err := initDBService.writePgsqlConfig(pgsqlConfig); err != nil {
return err
}
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
next = context.WithValue(next, "db", db)
return next, err
}
func (h PgsqlInitHandler) InitTables(ctx context.Context, inits initSlice) error {
return createTables(ctx, inits)
}
func (h PgsqlInitHandler) InitData(ctx context.Context, inits initSlice) error {
next, cancel := context.WithCancel(ctx)
defer func(c func()) { c() }(cancel)
for i := 0; i < len(inits); i++ {
if inits[i].DataInserted(next) {
color.Info.Printf(InitDataExist, Pgsql, inits[i].InitializerName())
continue
}
if n, err := inits[i].InitializeData(next); err != nil {
color.Info.Printf(InitDataFailed, Pgsql, err)
return err
} else {
next = n
color.Info.Printf(InitDataSuccess, Pgsql, inits[i].InitializerName())
}
}
color.Info.Printf(InitSuccess, Pgsql)
return nil
}
// initPgsqlData pgsql 初始化数据
// Author [SliverHorn](https://github.com/SliverHorn)
func (initDBService *InitDBService) initPgsqlData() error {
return model.PgsqlDataInitialize(
system.Api,
system.User,
system.Casbin,
system.BaseMenu,
system.Authority,
system.Dictionary,
system.UserAuthority,
system.DataAuthorities,
system.AuthoritiesMenus,
system.DictionaryDetail,
system.ViewAuthorityMenuPostgres,
example.FilePgsql,
)
}