mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-21 18:20:50 +00:00
Logging in on a deployment with enabledp: true ends on the login page. The login itself succeeds - sys_login_log records it - and then /api/v1/getinfo answers 401 "登录失败", which sends the browser straight back. The query behind it reads: SELECT * FROM sys_user WHERE sys_user.user_id = 1 AND 1 = 0 AND deleted_at = 0 The 1 = 0 comes from the data-permission scope. GetInfo asked for a permission with GetPermissionFromContext, but the group this route sits in installs only the JWT middleware - no PermissionAction - so nothing ever put one in the context and what came back was the zero value. An unset scope is not one of the five recognised ones, and since unknown scopes began failing closed rather than silently matching every row, that zero value now means "match nothing". The route was working by accident before, and only on deployments that enable data permissions: the repository default is enabledp: false, where Permission returns the query untouched. That is why the local suite and CI are both green and the demo site is not. Two different faults, so two different fixes: /getinfo reads the caller's own row - the id comes from the token. A data scope answers "whose rows may this user see", so there is nothing left for it to restrict, and applying one is not a stricter version of the query but a broken one: DataScopeSelf matches on create_by, and an account is created by whoever added it, so a scoped self-read would 401 every user who did not create their own account. It now goes through GetSelf, which does no scoping at all - which is how GetProfile has always read the same row. /sys-api is the opposite case. Its three handlers do read the permission, and they are listing and updating other people's rows, so the middleware belongs there and was simply missing. Added. Those four endpoints were found by checking every handler that reads the permission against the group it is registered on. The check reports four before this commit and none after. No test. Both paths need a *gorm.DB with sys_user and sys_role rows before they reach the line that matters, and this repository's CI has no database - `make build` is CGO_ENABLED=0 with no sqlite tag. What can be tested is the shape of the mistake rather than its effect, and that belongs in tools/checksilent as a rule of its own; it is not in this commit because a site that cannot be logged into should not wait for it.
305 lines
8.2 KiB
Go
305 lines
8.2 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"go-admin/app/admin/models"
|
|
"go-admin/app/admin/service/dto"
|
|
|
|
log "github.com/go-admin-team/go-admin-core/v2/logger"
|
|
"github.com/go-admin-team/go-admin-core/v2/sdk/pkg"
|
|
"github.com/go-admin-team/go-admin-core/v2/sdk/service"
|
|
"gorm.io/gorm"
|
|
|
|
"go-admin/common/actions"
|
|
cDto "go-admin/common/dto"
|
|
)
|
|
|
|
type SysUser struct {
|
|
service.Service
|
|
}
|
|
|
|
// GetPage 获取SysUser列表
|
|
func (e *SysUser) GetPage(c *dto.SysUserGetPageReq, p *actions.DataPermission, list *[]models.SysUser, count *int64) error {
|
|
var err error
|
|
var data models.SysUser
|
|
|
|
err = e.Orm.Debug().Preload("Dept").
|
|
Scopes(
|
|
cDto.MakeCondition(c.GetNeedSearch()),
|
|
cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
|
|
actions.Permission(data.TableName(), p),
|
|
).
|
|
Find(list).Limit(-1).Offset(-1).
|
|
Count(count).Error
|
|
if err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSelf 获取调用者自己的 SysUser 对象,不套数据权限
|
|
//
|
|
// The data scope answers "whose rows may this user see"; the caller here is
|
|
// reading their own, and the id comes from the token, so there is nothing left
|
|
// for a scope to restrict. Applying one is not a stricter version of this
|
|
// query - it is a broken one. DataScopeSelf matches on create_by, and a user
|
|
// account is created by whoever added it, so a scoped self-read would fail for
|
|
// every user who did not create their own account.
|
|
//
|
|
// GetProfile has always read the same row this way, with no scope at all.
|
|
func (e *SysUser) GetSelf(d *dto.SysUserById, model *models.SysUser) error {
|
|
err := e.Orm.First(model, d.GetId()).Error
|
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
|
err = errors.New("查看对象不存在或无权查看")
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
if err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get 获取SysUser对象
|
|
func (e *SysUser) Get(d *dto.SysUserById, p *actions.DataPermission, model *models.SysUser) error {
|
|
var data models.SysUser
|
|
|
|
err := e.Orm.Model(&data).Debug().
|
|
Scopes(
|
|
actions.Permission(data.TableName(), p),
|
|
).
|
|
First(model, d.GetId()).Error
|
|
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
|
|
err = errors.New("查看对象不存在或无权查看")
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
if err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Insert 创建SysUser对象
|
|
func (e *SysUser) Insert(c *dto.SysUserInsertReq) error {
|
|
var err error
|
|
var data models.SysUser
|
|
var i int64
|
|
err = e.Orm.Model(&data).Where("username = ?", c.Username).Count(&i).Error
|
|
if err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
if i > 0 {
|
|
err := errors.New("用户名已存在!")
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
c.Generate(&data)
|
|
err = e.Orm.Create(&data).Error
|
|
if err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Update 修改SysUser对象
|
|
//
|
|
// callerId is who is asking, not who SetUpdateBy recorded - that field only
|
|
// says who to blame, it never constrained who could be edited. When the
|
|
// target is the caller themselves, roleId/deptId/status are kept at whatever
|
|
// the database already has no matter what the request body carries: this is
|
|
// the personal-center screen's route (see CasbinExclude in settings.go, and
|
|
// the check in the API handler ahead of this call), and letting a caller
|
|
// grant themselves a different role or department through it would be a
|
|
// privilege escalation the exclusion was never meant to open.
|
|
func (e *SysUser) Update(c *dto.SysUserUpdateReq, p *actions.DataPermission, callerId int) error {
|
|
var err error
|
|
var model models.SysUser
|
|
db := e.Orm.Scopes(
|
|
actions.Permission(model.TableName(), p),
|
|
).First(&model, c.GetId())
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("Service UpdateSysUser error: %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
return errors.New("无权更新该数据")
|
|
|
|
}
|
|
if model.UserId == callerId {
|
|
c.RoleId = model.RoleId
|
|
c.DeptId = model.DeptId
|
|
c.Status = model.Status
|
|
}
|
|
c.Generate(&model)
|
|
update := e.Orm.Model(&model).Where("user_id = ?", &model.UserId).Omit("password", "salt").Updates(&model)
|
|
if err = update.Error; err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
if update.RowsAffected == 0 {
|
|
err = errors.New("update userinfo error")
|
|
log.Warnf("db update error")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateAvatar 更新用户头像
|
|
func (e *SysUser) UpdateAvatar(c *dto.UpdateSysUserAvatarReq, p *actions.DataPermission) error {
|
|
var err error
|
|
var model models.SysUser
|
|
db := e.Orm.Scopes(
|
|
actions.Permission(model.TableName(), p),
|
|
).First(&model, c.GetId())
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("Service UpdateSysUser error: %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
return errors.New("无权更新该数据")
|
|
|
|
}
|
|
err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error
|
|
if err != nil {
|
|
e.Log.Errorf("Service UpdateSysUser error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateStatus 更新用户状态
|
|
func (e *SysUser) UpdateStatus(c *dto.UpdateSysUserStatusReq, p *actions.DataPermission) error {
|
|
var err error
|
|
var model models.SysUser
|
|
db := e.Orm.Scopes(
|
|
actions.Permission(model.TableName(), p),
|
|
).First(&model, c.GetId())
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("Service UpdateSysUser error: %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
return errors.New("无权更新该数据")
|
|
|
|
}
|
|
err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error
|
|
if err != nil {
|
|
e.Log.Errorf("Service UpdateSysUser error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ResetPwd 重置用户密码
|
|
func (e *SysUser) ResetPwd(c *dto.ResetSysUserPwdReq, p *actions.DataPermission) error {
|
|
var err error
|
|
var model models.SysUser
|
|
db := e.Orm.Scopes(
|
|
actions.Permission(model.TableName(), p),
|
|
).First(&model, c.GetId())
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("At Service ResetSysUserPwd error: %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
return errors.New("无权更新该数据")
|
|
}
|
|
c.Generate(&model)
|
|
err = e.Orm.Omit("username", "nick_name", "phone", "role_id", "avatar", "sex").Save(&model).Error
|
|
if err != nil {
|
|
e.Log.Errorf("At Service ResetSysUserPwd error: %s", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Remove 删除SysUser
|
|
func (e *SysUser) Remove(c *dto.SysUserById, p *actions.DataPermission) error {
|
|
var err error
|
|
var data models.SysUser
|
|
|
|
db := e.Orm.Model(&data).
|
|
Scopes(
|
|
actions.Permission(data.TableName(), p),
|
|
).Delete(&data, c.GetId())
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("Error found in RemoveSysUser : %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
return errors.New("无权删除该数据")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdatePwd 修改SysUser对象密码
|
|
func (e *SysUser) UpdatePwd(id int, oldPassword, newPassword string, p *actions.DataPermission) error {
|
|
var err error
|
|
|
|
if newPassword == "" {
|
|
return nil
|
|
}
|
|
c := &models.SysUser{}
|
|
|
|
err = e.Orm.Model(c).
|
|
Scopes(
|
|
actions.Permission(c.TableName(), p),
|
|
).Select("UserId", "Password", "Salt").
|
|
First(c, id).Error
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return errors.New("无权更新该数据")
|
|
}
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
var ok bool
|
|
ok, err = pkg.CompareHashAndPassword(c.Password, oldPassword)
|
|
if err != nil {
|
|
e.Log.Errorf("CompareHashAndPassword error, %s", err.Error())
|
|
return err
|
|
}
|
|
if !ok {
|
|
err = errors.New("incorrect Password")
|
|
e.Log.Warnf("user[%d] %s", id, err.Error())
|
|
return err
|
|
}
|
|
c.Password = newPassword
|
|
db := e.Orm.Model(c).Where("user_id = ?", id).
|
|
Select("Password", "Salt").
|
|
Updates(c)
|
|
if err = db.Error; err != nil {
|
|
e.Log.Errorf("db error: %s", err)
|
|
return err
|
|
}
|
|
if db.RowsAffected == 0 {
|
|
err = errors.New("set password error")
|
|
log.Warnf("db update error")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *SysUser) GetProfile(c *dto.SysUserById, user *models.SysUser, roles *[]models.SysRole, posts *[]models.SysPost) error {
|
|
err := e.Orm.Preload("Dept").First(user, c.GetId()).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = e.Orm.Find(roles, user.RoleId).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = e.Orm.Find(posts, user.PostIds).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|