|
- package models
-
- import (
- "fmt"
- "os"
- "strings"
- "time"
-
- "github.com/ccfos/nightingale/v6/pkg/ctx"
- "github.com/ccfos/nightingale/v6/pkg/ormx"
- "github.com/ccfos/nightingale/v6/pkg/poster"
-
- "github.com/pkg/errors"
- "github.com/tidwall/gjson"
- "github.com/toolkits/pkg/logger"
- "github.com/toolkits/pkg/slice"
- "github.com/toolkits/pkg/str"
- "gorm.io/gorm"
- )
-
- const (
- Dingtalk = "dingtalk"
- Wecom = "wecom"
- Feishu = "feishu"
- FeishuCard = "feishucard"
- Mm = "mm"
- Telegram = "telegram"
- Email = "email"
- EmailSubject = "mailsubject"
-
- DingtalkKey = "dingtalk_robot_token"
- WecomKey = "wecom_robot_token"
- FeishuKey = "feishu_robot_token"
- MmKey = "mm_webhook_url"
- TelegramKey = "telegram_robot_token"
- )
-
- var (
- DefaultChannels = []string{Dingtalk, Wecom, Feishu, Mm, Telegram, Email, FeishuCard}
- )
-
- type User struct {
- Id int64 `json:"id" gorm:"primaryKey"`
- Username string `json:"username"`
- Nickname string `json:"nickname"`
- Password string `json:"-"`
- Phone string `json:"phone"`
- Email string `json:"email"`
- Portrait string `json:"portrait"`
- Roles string `json:"-"` // 这个字段写入数据库
- RolesLst []string `json:"roles" gorm:"-"` // 这个字段和前端交互
- Contacts ormx.JSONObj `json:"contacts"` // 内容为 map[string]string 结构
- Maintainer int `json:"maintainer"` // 是否给管理员发消息 0:not send 1:send
- CreateAt int64 `json:"create_at"`
- CreateBy string `json:"create_by"`
- UpdateAt int64 `json:"update_at"`
- UpdateBy string `json:"update_by"`
- Belong string `json:"belong"`
- Admin bool `json:"admin" gorm:"-"` // 方便前端使用
- }
-
- func (u *User) TableName() string {
- return "users"
- }
-
- func (u *User) DB2FE() error {
- return nil
- }
-
- func (u *User) String() string {
- bs, err := u.Contacts.MarshalJSON()
- if err != nil {
- return err.Error()
- }
-
- return fmt.Sprintf("<id:%d username:%s nickname:%s email:%s phone:%s contacts:%s>", u.Id, u.Username, u.Nickname, u.Email, u.Phone, string(bs))
- }
-
- func (u *User) IsAdmin() bool {
- for i := 0; i < len(u.RolesLst); i++ {
- if u.RolesLst[i] == AdminRole {
- return true
- }
- }
- return false
- }
-
- func (u *User) Verify() error {
- u.Username = strings.TrimSpace(u.Username)
-
- if u.Username == "" {
- return errors.New("Username is blank")
- }
-
- if str.Dangerous(u.Username) {
- return errors.New("Username has invalid characters")
- }
-
- if str.Dangerous(u.Nickname) {
- return errors.New("Nickname has invalid characters")
- }
-
- if u.Phone != "" && !str.IsPhone(u.Phone) {
- return errors.New("Phone invalid")
- }
-
- if u.Email != "" && !str.IsMail(u.Email) {
- return errors.New("Email invalid")
- }
-
- return nil
- }
-
- func (u *User) UpdateSsoFields(sso string, nickname, phone, email string) []interface{} {
- u.UpdateAt = time.Now().Unix()
-
- if nickname != "" {
- u.Nickname = nickname
- }
- if phone != "" {
- u.Phone = phone
- }
- if email != "" {
- u.Email = email
- }
- u.UpdateBy = sso
- u.Belong = sso
-
- updatedFields := []interface{}{"nickname", "phone", "email", "update_by", "belong"}
- return updatedFields
- }
-
- func (u *User) FullSsoFields(sso, username, nickname, phone, email string, defaultRoles []string) {
- now := time.Now().Unix()
-
- u.Username = username
- u.Password = "******"
- u.Nickname = nickname
- u.Phone = phone
- u.Email = email
- u.Portrait = ""
- u.Roles = strings.Join(defaultRoles, " ")
- u.RolesLst = defaultRoles
- u.Contacts = []byte("{}")
- u.CreateAt = now
- u.UpdateAt = now
- u.CreateBy = sso
- u.UpdateBy = sso
- u.Belong = sso
- }
-
- func (u *User) Add(ctx *ctx.Context) error {
- user, err := UserGetByUsername(ctx, u.Username)
- if err != nil {
- return errors.WithMessage(err, "failed to query user")
- }
-
- if user != nil {
- return errors.New("Username already exists")
- }
-
- now := time.Now().Unix()
- u.CreateAt = now
- u.UpdateAt = now
- return Insert(ctx, u)
- }
-
- func (u *User) Update(ctx *ctx.Context, selectField interface{}, selectFields ...interface{}) error {
- if err := u.Verify(); err != nil {
- return err
- }
-
- return DB(ctx).Model(u).Select(selectField, selectFields...).Updates(u).Error
- }
-
- func (u *User) UpdateAllFields(ctx *ctx.Context) error {
- if err := u.Verify(); err != nil {
- return err
- }
-
- u.UpdateAt = time.Now().Unix()
- return DB(ctx).Model(u).Select("*").Updates(u).Error
- }
-
- func (u *User) UpdatePassword(ctx *ctx.Context, password, updateBy string) error {
- return DB(ctx).Model(u).Updates(map[string]interface{}{
- "password": password,
- "update_at": time.Now().Unix(),
- "update_by": updateBy,
- }).Error
- }
-
- func (u *User) Del(ctx *ctx.Context) error {
- return DB(ctx).Transaction(func(tx *gorm.DB) error {
- if err := tx.Where("user_id=?", u.Id).Delete(&UserGroupMember{}).Error; err != nil {
- return err
- }
-
- if err := tx.Where("id=?", u.Id).Delete(&User{}).Error; err != nil {
- return err
- }
-
- return nil
- })
- }
-
- func (u *User) ChangePassword(ctx *ctx.Context, oldpass, newpass string) error {
- _oldpass, err := CryptoPass(ctx, oldpass)
- if err != nil {
- return err
- }
-
- _newpass, err := CryptoPass(ctx, newpass)
- if err != nil {
- return err
- }
-
- if u.Password != _oldpass {
- return errors.New("Incorrect old password")
- }
-
- return u.UpdatePassword(ctx, _newpass, u.Username)
- }
-
- func UserGet(ctx *ctx.Context, where string, args ...interface{}) (*User, error) {
- var lst []*User
- err := DB(ctx).Where(where, args...).Find(&lst).Error
- if err != nil {
- return nil, err
- }
-
- if len(lst) == 0 {
- return nil, nil
- }
-
- lst[0].RolesLst = strings.Fields(lst[0].Roles)
- lst[0].Admin = lst[0].IsAdmin()
-
- return lst[0], nil
- }
-
- func UserGetByUsername(ctx *ctx.Context, username string) (*User, error) {
- return UserGet(ctx, "username=?", username)
- }
-
- func UserGetById(ctx *ctx.Context, id int64) (*User, error) {
- return UserGet(ctx, "id=?", id)
- }
-
- func InitRoot(ctx *ctx.Context) {
- user, err := UserGetByUsername(ctx, "root")
- if err != nil {
- fmt.Println("failed to query user root:", err)
- os.Exit(1)
- }
-
- if user == nil {
- return
- }
-
- if len(user.Password) > 31 {
- // already done before
- return
- }
-
- newPass, err := CryptoPass(ctx, user.Password)
- if err != nil {
- fmt.Println("failed to crypto pass:", err)
- os.Exit(1)
- }
-
- err = DB(ctx).Model(user).Update("password", newPass).Error
- if err != nil {
- fmt.Println("failed to update root password:", err)
- os.Exit(1)
- }
-
- fmt.Println("root password init done")
- }
-
- func PassLogin(ctx *ctx.Context, username, pass string) (*User, error) {
- user, err := UserGetByUsername(ctx, username)
- if err != nil {
- return nil, err
- }
-
- if user == nil {
- return nil, fmt.Errorf("Username or password invalid")
- }
-
- loginPass, err := CryptoPass(ctx, pass)
- if err != nil {
- return nil, err
- }
-
- if loginPass != user.Password {
- return nil, fmt.Errorf("Username or password invalid")
- }
-
- return user, nil
- }
-
- func UserTotal(ctx *ctx.Context, query string) (num int64, err error) {
- if query != "" {
- q := "%" + query + "%"
- num, err = Count(DB(ctx).Model(&User{}).Where("username like ? or nickname like ? or phone like ? or email like ?", q, q, q, q))
- } else {
- num, err = Count(DB(ctx).Model(&User{}))
- }
-
- if err != nil {
- return num, errors.WithMessage(err, "failed to count user")
- }
-
- return num, nil
- }
-
- func UserGets(ctx *ctx.Context, query string, limit, offset int) ([]User, error) {
- session := DB(ctx).Limit(limit).Offset(offset).Order("username")
- if query != "" {
- q := "%" + query + "%"
- session = session.Where("username like ? or nickname like ? or phone like ? or email like ?", q, q, q, q)
- }
-
- var users []User
- err := session.Find(&users).Error
- if err != nil {
- return users, errors.WithMessage(err, "failed to query user")
- }
-
- for i := 0; i < len(users); i++ {
- users[i].RolesLst = strings.Fields(users[i].Roles)
- users[i].Admin = users[i].IsAdmin()
- users[i].Password = ""
- }
-
- return users, nil
- }
-
- func UserGetAll(ctx *ctx.Context) ([]*User, error) {
- if !ctx.IsCenter {
- lst, err := poster.GetByUrls[[]*User](ctx, "/v1/n9e/users")
- return lst, err
- }
-
- var lst []*User
- err := DB(ctx).Find(&lst).Error
- if err == nil {
- for i := 0; i < len(lst); i++ {
- lst[i].RolesLst = strings.Fields(lst[i].Roles)
- lst[i].Admin = lst[i].IsAdmin()
- }
- }
- return lst, err
- }
-
- func UserGetsByIds(ctx *ctx.Context, ids []int64) ([]User, error) {
- if len(ids) == 0 {
- return []User{}, nil
- }
-
- var lst []User
- err := DB(ctx).Where("id in ?", ids).Order("username").Find(&lst).Error
- if err == nil {
- for i := 0; i < len(lst); i++ {
- lst[i].RolesLst = strings.Fields(lst[i].Roles)
- lst[i].Admin = lst[i].IsAdmin()
- }
- }
-
- return lst, err
- }
-
- func UserGetsBySso(ctx *ctx.Context, sso string) (map[string]*User, error) {
- session := DB(ctx).Where("belong=?", sso).Order("username")
-
- var users []User
- err := session.Find(&users).Error
- if err != nil {
- return nil, errors.WithMessage(err, "failed to query user")
- }
-
- usersMap := make(map[string]*User, len(users))
- for i, user := range users {
- usersMap[user.Username] = &users[i]
- }
-
- return usersMap, nil
- }
-
- func UserDelByIds(ctx *ctx.Context, userIds []int64) error {
- return DB(ctx).Transaction(func(tx *gorm.DB) error {
- if err := tx.Where("user_id in ?", userIds).Delete(&UserGroupMember{}).Error; err != nil {
- return err
- }
-
- if err := tx.Where("id in ?", userIds).Delete(&User{}).Error; err != nil {
- return err
- }
-
- return nil
- })
- }
-
- func (u *User) CanModifyUserGroup(ctx *ctx.Context, ug *UserGroup) (bool, error) {
- // 我是管理员,自然可以
- if u.IsAdmin() {
- return true, nil
- }
-
- // 我是创建者,自然可以
- if ug.CreateBy == u.Username {
- return true, nil
- }
-
- // 我是成员,也可以吧,简单搞
- num, err := UserGroupMemberCount(ctx, "user_id=? and group_id=?", u.Id, ug.Id)
- if err != nil {
- return false, err
- }
-
- return num > 0, nil
- }
-
- func (u *User) CanDoBusiGroup(ctx *ctx.Context, bg *BusiGroup, permFlag ...string) (bool, error) {
- if u.IsAdmin() {
- return true, nil
- }
-
- // 我在任意一个UserGroup里,就有权限
- ugids, err := UserGroupIdsOfBusiGroup(ctx, bg.Id, permFlag...)
- if err != nil {
- return false, err
- }
-
- if len(ugids) == 0 {
- return false, nil
- }
-
- num, err := UserGroupMemberCount(ctx, "user_id = ? and group_id in ?", u.Id, ugids)
- return num > 0, err
- }
-
- func (u *User) CheckPerm(ctx *ctx.Context, operation string) (bool, error) {
- if u.IsAdmin() {
- return true, nil
- }
-
- return RoleHasOperation(ctx, u.RolesLst, operation)
- }
-
- func UserStatistics(ctx *ctx.Context) (*Statistics, error) {
- if !ctx.IsCenter {
- s, err := poster.GetByUrls[*Statistics](ctx, "/v1/n9e/statistic?name=user")
- return s, err
- }
-
- session := DB(ctx).Model(&User{}).Select("count(*) as total", "max(update_at) as last_updated")
-
- var stats []*Statistics
- err := session.Find(&stats).Error
- if err != nil {
- return nil, err
- }
-
- return stats[0], nil
- }
-
- func (u *User) NopriIdents(ctx *ctx.Context, idents []string) ([]string, error) {
- if u.IsAdmin() {
- return []string{}, nil
- }
-
- ugids, err := MyGroupIds(ctx, u.Id)
- if err != nil {
- return []string{}, err
- }
-
- if len(ugids) == 0 {
- return idents, nil
- }
-
- bgids, err := BusiGroupIds(ctx, ugids, "rw")
- if err != nil {
- return []string{}, err
- }
-
- if len(bgids) == 0 {
- return idents, nil
- }
-
- var arr []string
- err = DB(ctx).Model(&Target{}).Where("group_id in ?", bgids).Pluck("ident", &arr).Error
- if err != nil {
- return []string{}, err
- }
-
- return slice.SubString(idents, arr), nil
- }
-
- // 我是管理员,返回所有
- // 或者我是成员
- func (u *User) BusiGroups(ctx *ctx.Context, limit int, query string, all ...bool) ([]BusiGroup, error) {
- session := DB(ctx).Order("name").Limit(limit)
-
- var lst []BusiGroup
- if u.IsAdmin() || (len(all) > 0 && all[0]) {
- err := session.Where("name like ?", "%"+query+"%").Find(&lst).Error
- if err != nil {
- return lst, err
- }
-
- if len(lst) == 0 && len(query) > 0 {
- // 隐藏功能,一般人不告诉,哈哈。query可能是给的ident,所以上面的sql没有查到,当做ident来查一下试试
- var t *Target
- t, err = TargetGet(ctx, "ident=?", query)
- if err != nil {
- return lst, err
- }
-
- if t == nil {
- return lst, nil
- }
-
- err = DB(ctx).Order("name").Limit(limit).Where("id=?", t.GroupId).Find(&lst).Error
- }
-
- return lst, err
- }
-
- userGroupIds, err := MyGroupIds(ctx, u.Id)
- if err != nil {
- return nil, errors.WithMessage(err, "failed to get MyGroupIds")
- }
-
- busiGroupIds, err := BusiGroupIds(ctx, userGroupIds)
- if err != nil {
- return nil, errors.WithMessage(err, "failed to get BusiGroupIds")
- }
-
- if len(busiGroupIds) == 0 {
- return lst, nil
- }
-
- err = session.Where("id in ?", busiGroupIds).Where("name like ?", "%"+query+"%").Find(&lst).Error
- if err != nil {
- return nil, err
- }
-
- if len(lst) == 0 && len(query) > 0 {
- var t *Target
- t, err = TargetGet(ctx, "ident=?", query)
- if err != nil {
- return lst, err
- }
-
- if slice.ContainsInt64(busiGroupIds, t.GroupId) {
- err = DB(ctx).Order("name").Limit(limit).Where("id=?", t.GroupId).Find(&lst).Error
- }
- }
-
- return lst, err
- }
-
- func (u *User) UserGroups(ctx *ctx.Context, limit int, query string) ([]UserGroup, error) {
- session := DB(ctx).Order("name").Limit(limit)
-
- var lst []UserGroup
- if u.IsAdmin() {
- err := session.Where("name like ?", "%"+query+"%").Find(&lst).Error
- if err != nil {
- return lst, err
- }
-
- var user *User
- if len(lst) == 0 && len(query) > 0 {
- // 隐藏功能,一般人不告诉,哈哈。query可能是给的用户名,所以上面的sql没有查到,当做user来查一下试试
- user, err = UserGetByUsername(ctx, query)
- if user == nil {
- return lst, err
- }
- var ids []int64
- ids, err = MyGroupIds(ctx, user.Id)
- if err != nil || len(ids) == 0 {
- return lst, err
- }
- lst, err = UserGroupGetByIds(ctx, ids)
- }
- return lst, err
- }
-
- ids, err := MyGroupIds(ctx, u.Id)
- if err != nil {
- return nil, errors.WithMessage(err, "failed to get MyGroupIds")
- }
-
- if len(ids) > 0 {
- session = session.Where("id in ? or create_by = ?", ids, u.Username)
- } else {
- session = session.Where("create_by = ?", u.Username)
- }
-
- if len(query) > 0 {
- session = session.Where("name like ?", "%"+query+"%")
- }
-
- err = session.Find(&lst).Error
- return lst, err
- }
-
- func (u *User) ExtractToken(key string) (string, bool) {
- bs, err := u.Contacts.MarshalJSON()
- if err != nil {
- logger.Errorf("handle_notice: failed to marshal contacts: %v", err)
- return "", false
- }
-
- switch key {
- case Dingtalk:
- ret := gjson.GetBytes(bs, DingtalkKey)
- return ret.String(), ret.Exists()
- case Wecom:
- ret := gjson.GetBytes(bs, WecomKey)
- return ret.String(), ret.Exists()
- case Feishu, FeishuCard:
- ret := gjson.GetBytes(bs, FeishuKey)
- return ret.String(), ret.Exists()
- case Mm:
- ret := gjson.GetBytes(bs, MmKey)
- return ret.String(), ret.Exists()
- case Telegram:
- ret := gjson.GetBytes(bs, TelegramKey)
- return ret.String(), ret.Exists()
- case Email:
- return u.Email, u.Email != ""
- default:
- return "", false
- }
- }
-
- func (u *User) FindSameContact(email, phone string) string {
- if u.Email != "" && u.Email == email {
- return "email"
- }
-
- if u.Phone != "" && u.Phone == phone {
- return "phone"
- }
-
- return ""
- }
|