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.
490 lines
12 KiB
Go
490 lines
12 KiB
Go
package apis
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/gin-gonic/gin/binding"
|
|
"go-admin/app/admin/models"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-admin-team/go-admin-core/v2/jwtauth/user"
|
|
_ "github.com/go-admin-team/go-admin-core/v2/response"
|
|
"github.com/go-admin-team/go-admin-core/v2/sdk/api"
|
|
"github.com/google/uuid"
|
|
|
|
"go-admin/app/admin/service"
|
|
"go-admin/app/admin/service/dto"
|
|
"go-admin/common/actions"
|
|
"go-admin/common/middleware"
|
|
)
|
|
|
|
type SysUser struct {
|
|
api.Api
|
|
}
|
|
|
|
// GetPage
|
|
// @Summary 列表用户信息数据
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Param username query string false "username"
|
|
// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/sys-user [get]
|
|
// @Security Bearer
|
|
func (e SysUser) GetPage(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserGetPageReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
//数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
|
|
list := make([]models.SysUser, 0)
|
|
var count int64
|
|
|
|
err = s.GetPage(&req, p, &list, &count)
|
|
if err != nil {
|
|
e.Error(500, err, "查询失败")
|
|
return
|
|
}
|
|
|
|
e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
|
|
}
|
|
|
|
// Get
|
|
// @Summary 获取用户
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Param userId path int true "用户编码"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/sys-user/{userId} [get]
|
|
// @Security Bearer
|
|
func (e SysUser) Get(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserById{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req, nil).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
var object models.SysUser
|
|
//数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
err = s.Get(&req, p, &object)
|
|
if err != nil {
|
|
e.Error(http.StatusUnprocessableEntity, err, "查询失败")
|
|
return
|
|
}
|
|
e.OK(object, "查询成功")
|
|
}
|
|
|
|
// Insert
|
|
// @Summary 创建用户
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Accept application/json
|
|
// @Product application/json
|
|
// @Param data body dto.SysUserInsertReq true "用户数据"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/sys-user [post]
|
|
// @Security Bearer
|
|
func (e SysUser) Insert(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserInsertReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req, binding.JSON).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
// 设置创建人
|
|
req.SetCreateBy(user.GetUserId(c))
|
|
err = s.Insert(&req)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
e.OK(req.GetId(), "创建成功")
|
|
}
|
|
|
|
// Update
|
|
// @Summary 修改用户数据
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Accept application/json
|
|
// @Product application/json
|
|
// @Param data body dto.SysUserUpdateReq true "body"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/sys-user/{userId} [put]
|
|
// @Security Bearer
|
|
func (e SysUser) Update(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserUpdateReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
callerId := user.GetUserId(c)
|
|
|
|
// This route is in CasbinExclude so the personal-center screen can edit
|
|
// the caller's own record without a policy grant (see settings.go). That
|
|
// exclusion covers the whole route, not just the caller's own record, and
|
|
// the request carries the target userId in the body - so without this
|
|
// check here, any authenticated caller could edit any other user, up to
|
|
// and including their roleId. When the target is someone else, ask Casbin
|
|
// directly for the permission AuthCheckRole skipped.
|
|
if req.UserId != callerId {
|
|
allowed, err := middleware.EnforceRoleFor(c, c.Request.URL.Path, c.Request.Method)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
if !allowed {
|
|
e.Error(http.StatusForbidden, errors.New("无权更新其他用户数据"), "对不起,您没有该接口访问权限,请联系管理员")
|
|
return
|
|
}
|
|
}
|
|
|
|
req.SetUpdateBy(callerId)
|
|
|
|
//数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
|
|
err = s.Update(&req, p, callerId)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
return
|
|
}
|
|
e.OK(req.GetId(), "更新成功")
|
|
}
|
|
|
|
// Delete
|
|
// @Summary 删除用户数据
|
|
// @Description 删除数据
|
|
// @Tags 用户
|
|
// @Param userId path int true "userId"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/sys-user/{userId} [delete]
|
|
// @Security Bearer
|
|
func (e SysUser) Delete(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserById{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req, binding.JSON).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
// 设置编辑人
|
|
req.SetUpdateBy(user.GetUserId(c))
|
|
|
|
// 数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
|
|
err = s.Remove(&req, p)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
return
|
|
}
|
|
e.OK(req.GetId(), "删除成功")
|
|
}
|
|
|
|
// InsetAvatar
|
|
// @Summary 修改头像
|
|
// @Description 获取JSON
|
|
// @Tags 个人中心
|
|
// @Accept multipart/form-data
|
|
// @Param file formData file true "file"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/user/avatar [post]
|
|
// @Security Bearer
|
|
func (e SysUser) InsetAvatar(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.UpdateSysUserAvatarReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
// 数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
form, _ := c.MultipartForm()
|
|
files := form.File["upload[]"]
|
|
guid := uuid.New().String()
|
|
filPath := "static/uploadfile/" + guid + ".jpg"
|
|
for _, file := range files {
|
|
e.Logger.Debugf("upload avatar file: %s", file.Filename)
|
|
// 上传文件至指定目录
|
|
err = c.SaveUploadedFile(file, filPath)
|
|
if err != nil {
|
|
e.Logger.Errorf("save file error, %s", err.Error())
|
|
e.Error(500, err, "")
|
|
return
|
|
}
|
|
}
|
|
req.UserId = p.UserId
|
|
req.Avatar = "/" + filPath
|
|
|
|
err = s.UpdateAvatar(&req, p)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
return
|
|
}
|
|
e.OK(filPath, "修改成功")
|
|
}
|
|
|
|
// UpdateStatus 修改用户状态
|
|
// @Summary 修改用户状态
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Accept application/json
|
|
// @Product application/json
|
|
// @Param data body dto.UpdateSysUserStatusReq true "body"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/user/status [put]
|
|
// @Security Bearer
|
|
func (e SysUser) UpdateStatus(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.UpdateSysUserStatusReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req, binding.JSON, nil).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
req.SetUpdateBy(user.GetUserId(c))
|
|
|
|
//数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
|
|
err = s.UpdateStatus(&req, p)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
return
|
|
}
|
|
e.OK(req.GetId(), "更新成功")
|
|
}
|
|
|
|
// ResetPwd 重置用户密码
|
|
// @Summary 重置用户密码
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Accept application/json
|
|
// @Product application/json
|
|
// @Param data body dto.ResetSysUserPwdReq true "body"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/user/pwd/reset [put]
|
|
// @Security Bearer
|
|
func (e SysUser) ResetPwd(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.ResetSysUserPwdReq{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req, binding.JSON).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
req.SetUpdateBy(user.GetUserId(c))
|
|
|
|
//数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
|
|
err = s.ResetPwd(&req, p)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
return
|
|
}
|
|
e.OK(req.GetId(), "更新成功")
|
|
}
|
|
|
|
// UpdatePwd
|
|
// @Summary 修改密码
|
|
// @Description 获取JSON
|
|
// @Tags 用户
|
|
// @Accept application/json
|
|
// @Product application/json
|
|
// @Param data body dto.PassWord true "body"
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/user/pwd/set [put]
|
|
// @Security Bearer
|
|
func (e SysUser) UpdatePwd(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.PassWord{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
Bind(&req).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
// 数据权限检查
|
|
p := actions.GetPermissionFromContext(c)
|
|
var hash []byte
|
|
if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil {
|
|
req.NewPassword = string(hash)
|
|
}
|
|
|
|
err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p)
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(http.StatusForbidden, err, "密码修改失败")
|
|
return
|
|
}
|
|
|
|
e.OK(nil, "密码修改成功")
|
|
}
|
|
|
|
// GetProfile
|
|
// @Summary 获取个人中心用户
|
|
// @Description 获取JSON
|
|
// @Tags 个人中心
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/user/profile [get]
|
|
// @Security Bearer
|
|
func (e SysUser) GetProfile(c *gin.Context) {
|
|
s := service.SysUser{}
|
|
req := dto.SysUserById{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
|
|
req.Id = user.GetUserId(c)
|
|
|
|
sysUser := models.SysUser{}
|
|
roles := make([]models.SysRole, 0)
|
|
posts := make([]models.SysPost, 0)
|
|
err = s.GetProfile(&req, &sysUser, &roles, &posts)
|
|
if err != nil {
|
|
e.Logger.Errorf("get user profile error, %s", err.Error())
|
|
e.Error(500, err, "获取用户信息失败")
|
|
return
|
|
}
|
|
e.OK(gin.H{
|
|
"user": sysUser,
|
|
"roles": roles,
|
|
"posts": posts,
|
|
}, "查询成功")
|
|
}
|
|
|
|
// GetInfo
|
|
// @Summary 获取个人信息
|
|
// @Description 获取JSON
|
|
// @Tags 个人中心
|
|
// @Success 200 {object} response.Response "{"code": 200, "data": [...]}"
|
|
// @Router /api/v1/getinfo [get]
|
|
// @Security Bearer
|
|
func (e SysUser) GetInfo(c *gin.Context) {
|
|
req := dto.SysUserById{}
|
|
s := service.SysUser{}
|
|
r := service.SysRole{}
|
|
err := e.MakeContext(c).
|
|
MakeOrm().
|
|
MakeService(&r.Service).
|
|
MakeService(&s.Service).
|
|
Errors
|
|
if err != nil {
|
|
e.Logger.Error(err)
|
|
e.Error(500, err, err.Error())
|
|
return
|
|
}
|
|
var roles = make([]string, 1)
|
|
roles[0] = user.GetRoleName(c)
|
|
var permissions = make([]string, 1)
|
|
permissions[0] = "*:*:*"
|
|
var buttons = make([]string, 1)
|
|
buttons[0] = "*:*:*"
|
|
|
|
var mp = make(map[string]interface{})
|
|
mp["roles"] = roles
|
|
if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" {
|
|
mp["permissions"] = permissions
|
|
mp["buttons"] = buttons
|
|
} else {
|
|
list, _ := r.GetById(user.GetRoleId(c))
|
|
mp["permissions"] = list
|
|
mp["buttons"] = list
|
|
}
|
|
sysUser := models.SysUser{}
|
|
req.Id = user.GetUserId(c)
|
|
// Unscoped on purpose: the id is the caller's own, taken from the token.
|
|
// This used to go through Get with whatever GetPermissionFromContext
|
|
// returned - and this route installs no PermissionAction, so that was the
|
|
// zero value. An unset scope is not a recognised one, so once unknown
|
|
// scopes started failing closed rather than silently matching everything,
|
|
// every login on a deployment with enabledp: true ended here with a 401
|
|
// and the browser went straight back to the login page.
|
|
err = s.GetSelf(&req, &sysUser)
|
|
if err != nil {
|
|
e.Error(http.StatusUnauthorized, err, "登录失败")
|
|
return
|
|
}
|
|
mp["introduction"] = " am a super administrator"
|
|
mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"
|
|
if sysUser.Avatar != "" {
|
|
mp["avatar"] = sysUser.Avatar
|
|
}
|
|
mp["userName"] = sysUser.Username
|
|
mp["userId"] = sysUser.UserId
|
|
mp["deptId"] = sysUser.DeptId
|
|
mp["name"] = sysUser.NickName
|
|
mp["code"] = 200
|
|
e.OK(mp, "")
|
|
}
|