diff --git a/server/internal/handler/config.go b/server/internal/handler/config.go
index 0daa12d..b0cce72 100644
--- a/server/internal/handler/config.go
+++ b/server/internal/handler/config.go
@@ -149,7 +149,7 @@ func (h *ConfigHandler) GetById(c *gin.Context) {
}
userInfo := middleware.GetUserInfo(c)
- if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RO"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
@@ -183,7 +183,7 @@ func (h *ConfigHandler) Add(c *gin.Context) {
c.JSON(http.StatusOK, model.WrapperResponse{Code: 400, Msg: "全局配置需要指定所属业务组"})
return
}
- if err := h.permService.RequirePermission(userInfo, req.GroupId, nil, "RW"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, req.GroupId, nil); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
@@ -264,7 +264,7 @@ func (h *ConfigHandler) Update(c *gin.Context) {
}
userInfo := middleware.GetUserInfo(c)
- if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
@@ -311,7 +311,7 @@ func (h *ConfigHandler) Delete(c *gin.Context) {
}
userInfo := middleware.GetUserInfo(c)
- if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
@@ -335,19 +335,51 @@ func (h *ConfigHandler) Delete(c *gin.Context) {
func (h *ConfigHandler) GetHistory(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
- var histories []model.AppConfigHistory
- dao.Engine.Where("origin_id = ?", id).OrderBy("id desc").Find(&histories)
- if len(histories) > 0 {
- var currentConfig model.AppConfig
- if has, _ := dao.Engine.ID(histories[0].OriginId).Get(¤tConfig); has {
- for i := range histories {
- histories[i].ActiveContents = currentConfig.Contents
- }
+ var config model.AppConfig
+ has, _ := dao.Engine.ID(id).Get(&config)
+
+ userInfo := middleware.GetUserInfo(c)
+ if has {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
+ c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
+ return
+ }
+ } else if !userInfo.IsSuperAdmin() {
+ // 配置已被物理删除时,只有超级管理员可以查看残留历史
+ c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: "只有超级管理员才能查看该配置历史"})
+ return
+ }
+
+ pageNo, _ := strconv.Atoi(c.DefaultQuery("pageNo", "1"))
+ pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
+ if pageNo < 1 {
+ pageNo = 1
+ }
+ if pageSize < 1 {
+ pageSize = 10
+ }
+ if pageSize > 200 {
+ pageSize = 200
+ }
+
+ histories := make([]model.AppConfigHistory, 0)
+ total, _ := dao.Engine.Where("origin_id = ?", id).Count(&model.AppConfigHistory{})
+ dao.Engine.Where("origin_id = ?", id).OrderBy("id desc").
+ Limit(pageSize, (pageNo-1)*pageSize).Find(&histories)
+
+ if has {
+ for i := range histories {
+ histories[i].ActiveContents = config.Contents
}
}
- c.JSON(http.StatusOK, model.WrapperResponse{Code: 0, Data: histories})
+ c.JSON(http.StatusOK, model.PageResult{
+ PageNo: pageNo,
+ PageSize: pageSize,
+ Total: total,
+ Data: histories,
+ })
}
// DeleteHistory 手动删除单条配置历史
@@ -363,7 +395,7 @@ func (h *ConfigHandler) DeleteHistory(c *gin.Context) {
userInfo := middleware.GetUserInfo(c)
var config model.AppConfig
if has, _ := dao.Engine.ID(history.OriginId).Get(&config); has {
- if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
@@ -394,7 +426,7 @@ func (h *ConfigHandler) Rollback(c *gin.Context) {
var config model.AppConfig
if has, _ := dao.Engine.ID(history.OriginId).Get(&config); has {
userInfo := middleware.GetUserInfo(c)
- if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId, "RW"); err != nil {
+ if err := h.permService.RequirePermission(userInfo, config.GroupId, config.AppId); err != nil {
c.JSON(http.StatusForbidden, model.WrapperResponse{Code: 403, Msg: err.Error()})
return
}
diff --git a/server/internal/model/user.go b/server/internal/model/user.go
index 0dbb9c1..b634bd0 100644
--- a/server/internal/model/user.go
+++ b/server/internal/model/user.go
@@ -23,13 +23,12 @@ func (User) TableName() string {
}
type LoginUserInfo struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Type string `json:"type"`
- GroupId *int `json:"groupId"`
- GrantAppIds []int `json:"grantAppIds"`
- GrantPermissons []string `json:"grantPermissons"`
- GroupMaster bool `json:"groupMaster"`
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ GroupId *int `json:"groupId"`
+ GrantAppIds []int `json:"grantAppIds"`
+ GroupMaster bool `json:"groupMaster"`
}
func (u *LoginUserInfo) IsSuperAdmin() bool {
diff --git a/server/internal/service/auth.go b/server/internal/service/auth.go
index 3d9973d..4027e2a 100644
--- a/server/internal/service/auth.go
+++ b/server/internal/service/auth.go
@@ -45,12 +45,11 @@ func (s *AuthService) Login(userName, password string) (string, *model.LoginUser
}
userInfo := &model.LoginUserInfo{
- Id: strconv.Itoa(user.Id),
- Name: user.Name,
- Type: user.Type,
- GroupId: user.GroupId,
- GrantAppIds: make([]int, 0),
- GrantPermissons: make([]string, 0),
+ Id: strconv.Itoa(user.Id),
+ Name: user.Name,
+ Type: user.Type,
+ GroupId: user.GroupId,
+ GrantAppIds: make([]int, 0),
}
// load permissions
@@ -120,12 +119,11 @@ func (s *AuthService) ValidateToken(tokenString string) (*model.LoginUserInfo, e
}
userInfo := &model.LoginUserInfo{
- Id: strconv.Itoa(user.Id),
- Name: user.Name,
- Type: user.Type,
- GroupId: user.GroupId,
- GrantAppIds: make([]int, 0),
- GrantPermissons: make([]string, 0),
+ Id: strconv.Itoa(user.Id),
+ Name: user.Name,
+ Type: user.Type,
+ GroupId: user.GroupId,
+ GrantAppIds: make([]int, 0),
}
s.loadPermissions(userInfo, user.Id)
return userInfo, nil
@@ -146,10 +144,5 @@ func (s *AuthService) loadPermissions(userInfo *model.LoginUserInfo, userId int)
}
for _, app := range apps {
userInfo.GrantAppIds = append(userInfo.GrantAppIds, app.Id)
- userInfo.GrantPermissons = append(userInfo.GrantPermissons, buildPermissionCode(app.Id, "RW"))
}
}
-
-func buildPermissionCode(appId int, operate string) string {
- return strconv.Itoa(appId) + ":" + operate
-}
diff --git a/server/internal/service/permission.go b/server/internal/service/permission.go
index 024f6e5..b2f30e0 100644
--- a/server/internal/service/permission.go
+++ b/server/internal/service/permission.go
@@ -18,19 +18,18 @@ func (s *PermissionService) RequireSuperAdmin(userInfo *model.LoginUserInfo) err
return nil
}
-func (s *PermissionService) RequirePermission(userInfo *model.LoginUserInfo, groupId *int, appId *int, operate string) error {
+func (s *PermissionService) RequirePermission(userInfo *model.LoginUserInfo, groupId *int, appId *int) error {
if userInfo.IsSuperAdmin() {
return nil
}
- if groupId != nil && userInfo.GroupId != nil && *groupId == *userInfo.GroupId {
+ if userInfo.IsGroupAdmin() && groupId != nil && userInfo.GroupId != nil && *groupId == *userInfo.GroupId {
return nil
}
if appId == nil || *appId == 0 {
return errors.New("你没有全局配置权限")
}
- permCode := buildPermissionCode(*appId, operate)
- for _, p := range userInfo.GrantPermissons {
- if p == permCode {
+ for _, id := range userInfo.GrantAppIds {
+ if id == *appId {
return nil
}
}
diff --git a/server/internal/service/permission_test.go b/server/internal/service/permission_test.go
new file mode 100644
index 0000000..7f2348e
--- /dev/null
+++ b/server/internal/service/permission_test.go
@@ -0,0 +1,93 @@
+package service
+
+import (
+ "testing"
+
+ "kenaito-conf/server/internal/model"
+)
+
+func TestRequirePermission(t *testing.T) {
+ svc := NewPermissionService()
+ groupID := 1
+ otherGroupID := 2
+ appID := 10
+
+ tests := []struct {
+ name string
+ user *model.LoginUserInfo
+ groupId *int
+ appId *int
+ wantErr bool
+ }{
+ {
+ name: "super admin can access anything",
+ user: &model.LoginUserInfo{Type: "superAdmin"},
+ groupId: &groupID,
+ appId: &appID,
+ },
+ {
+ name: "group admin can access own group",
+ user: &model.LoginUserInfo{Type: "groupAdmin", GroupId: &groupID},
+ groupId: &groupID,
+ appId: &appID,
+ },
+ {
+ name: "group admin cannot access other group",
+ user: &model.LoginUserInfo{Type: "groupAdmin", GroupId: &groupID},
+ groupId: &otherGroupID,
+ appId: &appID,
+ wantErr: true,
+ },
+ {
+ name: "owner can access app",
+ user: &model.LoginUserInfo{Type: "user", GroupId: &groupID, GrantAppIds: []int{appID}},
+ groupId: &groupID,
+ appId: &appID,
+ },
+ {
+ name: "owner can access app across group",
+ user: &model.LoginUserInfo{Type: "user", GroupId: &otherGroupID, GrantAppIds: []int{appID}},
+ groupId: &groupID,
+ appId: &appID,
+ },
+ {
+ name: "normal group member cannot access app without ownership",
+ user: &model.LoginUserInfo{Type: "user", GroupId: &groupID},
+ groupId: &groupID,
+ appId: &appID,
+ wantErr: true,
+ },
+ {
+ name: "normal user cannot access global config",
+ user: &model.LoginUserInfo{Type: "user", GroupId: &groupID},
+ groupId: &groupID,
+ appId: nil,
+ wantErr: true,
+ },
+ {
+ name: "group admin can access own global config",
+ user: &model.LoginUserInfo{Type: "groupAdmin", GroupId: &groupID},
+ groupId: &groupID,
+ appId: nil,
+ },
+ {
+ name: "group admin cannot access other global config",
+ user: &model.LoginUserInfo{Type: "groupAdmin", GroupId: &groupID},
+ groupId: &otherGroupID,
+ appId: nil,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := svc.RequirePermission(tt.user, tt.groupId, tt.appId)
+ if tt.wantErr && err == nil {
+ t.Fatalf("expected an error, got nil")
+ }
+ if !tt.wantErr && err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ })
+ }
+}
diff --git a/web/src/api/config.js b/web/src/api/config.js
index f39405c..6882ae8 100644
--- a/web/src/api/config.js
+++ b/web/src/api/config.js
@@ -38,10 +38,11 @@ export function deleteConfig(id) {
})
}
-export function getConfigHistory(id) {
+export function getConfigHistory(id, params) {
return request({
url: `/admin/config/configHistories/${id}`,
- method: 'get'
+ method: 'get',
+ params
})
}
diff --git a/web/src/views/config/AllConfigList.vue b/web/src/views/config/AllConfigList.vue
index 9a04936..965a905 100644
--- a/web/src/views/config/AllConfigList.vue
+++ b/web/src/views/config/AllConfigList.vue
@@ -86,6 +86,15 @@
+
@@ -247,6 +256,8 @@ export default {
apps: [],
historyVisible: false,
historyList: [],
+ historyTotal: 0,
+ historyQuery: { pageNo: 1, pageSize: 10 },
currentConfig: null,
contentVisible: false,
currentHistory: {},
@@ -316,19 +327,31 @@ export default {
},
async showHistory(row) {
this.currentConfig = row
+ this.historyQuery.pageNo = 1
+ await this.loadHistory()
+ this.historyVisible = true
+ },
+ async loadHistory() {
+ if (!this.currentConfig) return
try {
- const res = await getConfigHistory(row.id)
+ const res = await getConfigHistory(this.currentConfig.id, this.historyQuery)
this.historyList = res.data || []
- this.historyVisible = true
+ this.historyTotal = res.total || 0
} catch (e) {}
},
+ handleHistoryPageChange(page) {
+ this.historyQuery.pageNo = page
+ this.loadHistory()
+ },
async handleDeleteHistory(row) {
try {
await this.$confirm('确定删除这条历史记录?删除后不可恢复。')
await deleteConfigHistory(row.id)
this.$message.success('删除成功')
- const res = await getConfigHistory(row.originId)
- this.historyList = res.data || []
+ if (this.historyList.length === 1 && this.historyQuery.pageNo > 1) {
+ this.historyQuery.pageNo--
+ }
+ await this.loadHistory()
} catch (e) {}
},
async viewHistoryContent(row) {
diff --git a/web/src/views/config/ConfigList.vue b/web/src/views/config/ConfigList.vue
index aa3e2c2..60ee422 100644
--- a/web/src/views/config/ConfigList.vue
+++ b/web/src/views/config/ConfigList.vue
@@ -81,6 +81,15 @@
+
@@ -241,6 +250,8 @@ export default {
profiles: [],
historyVisible: false,
historyList: [],
+ historyTotal: 0,
+ historyQuery: { pageNo: 1, pageSize: 10 },
currentConfig: null,
contentVisible: false,
currentHistory: {},
@@ -302,19 +313,31 @@ export default {
},
async showHistory(row) {
this.currentConfig = row
+ this.historyQuery.pageNo = 1
+ await this.loadHistory()
+ this.historyVisible = true
+ },
+ async loadHistory() {
+ if (!this.currentConfig) return
try {
- const res = await getConfigHistory(row.id)
+ const res = await getConfigHistory(this.currentConfig.id, this.historyQuery)
this.historyList = res.data || []
- this.historyVisible = true
+ this.historyTotal = res.total || 0
} catch (e) {}
},
+ handleHistoryPageChange(page) {
+ this.historyQuery.pageNo = page
+ this.loadHistory()
+ },
async handleDeleteHistory(row) {
try {
await this.$confirm('确定删除这条历史记录?删除后不可恢复。')
await deleteConfigHistory(row.id)
this.$message.success('删除成功')
- const res = await getConfigHistory(row.originId)
- this.historyList = res.data || []
+ if (this.historyList.length === 1 && this.historyQuery.pageNo > 1) {
+ this.historyQuery.pageNo--
+ }
+ await this.loadHistory()
} catch (e) {}
},
async viewHistoryContent(row) {