From 91916cf9fd4f34eec583f0d29bcbd489743eebd1 Mon Sep 17 00:00:00 2001 From: xinyuandd Date: Sat, 28 May 2022 22:22:36 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=88=86=E7=BB=84=E6=B7=BB=E5=8A=A0O?= =?UTF-8?q?U=E5=92=8CCN=E6=A8=A1=E5=BC=8F=EF=BC=8CCN=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B8=BA=EF=BC=9AObjectClasses=EF=BC=9Agroup?= =?UTF-8?q?OfUniqueNames=EF=BC=8CuniqueMember=EF=BC=9Auid=3D{username},ou?= =?UTF-8?q?=3Dpeople,dc=3D{dc},dc=3D{dc}=202=E3=80=81=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=92=8C=E4=BF=AE=E6=94=B9=E4=BA=BA=E5=91=98?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=98=BE=E7=A4=BA=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?mail=E5=8C=B9=E9=85=8D=E5=BC=82=E5=B8=B8bug=203=E3=80=81?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=96=B0=E5=A2=9E=E4=BA=BA=E5=91=98=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E4=B8=AD=E9=83=A8=E9=97=A8=E4=BB=8E=E5=88=86=E7=BB=84?= =?UTF-8?q?=E4=B8=AD=E8=BF=9B=E8=A1=8C=E9=80=89=E6=8B=A9=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E9=80=89=E6=8B=A9CN=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E9=97=A8=E8=BF=9B=E8=A1=8C=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=204=E3=80=81=E6=94=AF=E6=8C=81=E4=BF=AE=E6=94=B9=E4=BA=BA?= =?UTF-8?q?=E5=91=98username=205=E3=80=81=E6=94=AF=E6=8C=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=88=86=E7=BB=84=E5=90=8D=E7=A7=B0=EF=BC=9Agroupname?= =?UTF-8?q?=206=E3=80=81=E6=94=AF=E6=8C=81=E5=88=9B=E5=BB=BA=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=88=86=E7=BB=84=EF=BC=8C=E7=B1=BB=E4=BC=BC=E4=BA=8E?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E7=BB=84=E7=BB=87=E6=9E=B6=E6=9E=84=E7=9A=84?= =?UTF-8?q?=E6=A0=91=E7=8A=B6=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.yml | 6 +- config/config.go | 14 +-- controller/group_controller.go | 14 +-- docs/docker/config.yml | 4 +- go.mod | 24 +---- logic/group_logic.go | 92 ++++++++++++++++--- logic/user_logic.go | 148 ++++++++++++++++++++++++++++--- model/group.go | 11 ++- model/user.go | 32 +++---- public/common/init_mysql_data.go | 8 ++ public/tools/web.go | 2 +- routes/group_routes.go | 1 + service/ildap/group_ildap.go | 76 +++++++++++----- service/ildap/user_ildap.go | 12 ++- service/isql/group_isql.go | 38 ++++++++ service/isql/user_isql.go | 44 +++++++++ svc/request/group_req.go | 14 ++- svc/request/user_req.go | 19 ++-- 18 files changed, 441 insertions(+), 118 deletions(-) diff --git a/config.yml b/config.yml index f6d7d89..0ff7363 100644 --- a/config.yml +++ b/config.yml @@ -31,9 +31,9 @@ mysql: # 用户名 username: root # 密码 - password: 123465 + password: 123456 # 数据库名 - database: go-ldap-admin + database: go_ldap_admin # 主机地址 host: localhost # 端口 @@ -90,3 +90,5 @@ ldap: ldap-admin-pass: "123456" ldap-user-dn: "ou=people,dc=eryajf,dc=net" ldap-group-dn: "ou=group,dc=eryajf,dc=net" + ldap-group-name-modify: false + ldap-user-name-modify: false diff --git a/config/config.go b/config/config.go index c865489..bdb3882 100644 --- a/config/config.go +++ b/config/config.go @@ -128,12 +128,14 @@ type RateLimitConfig struct { } type LdapConfig struct { - LdapUrl string `mapstructure:"ldap-url" json:"ldapUrl"` - LdapBaseDN string `mapstructure:"ldap-base-dn" json:"ldapBaseDN"` - LdapAdminDN string `mapstructure:"ldap-admin-dn" json:"ldapAdminDN"` - LdapAdminPass string `mapstructure:"ldap-admin-pass" json:"ldapAdminPass"` - LdapUserDN string `mapstructure:"ldap-user-dn" json:"ldapUserDN"` - LdapGroupDN string `mapstructure:"ldap-group-dn" json:"ldapGroupDN"` + LdapUrl string `mapstructure:"ldap-url" json:"ldapUrl"` + LdapBaseDN string `mapstructure:"ldap-base-dn" json:"ldapBaseDN"` + LdapAdminDN string `mapstructure:"ldap-admin-dn" json:"ldapAdminDN"` + LdapAdminPass string `mapstructure:"ldap-admin-pass" json:"ldapAdminPass"` + LdapUserDN string `mapstructure:"ldap-user-dn" json:"ldapUserDN"` + LdapGroupDN string `mapstructure:"ldap-group-dn" json:"ldapGroupDN"` + LdapGroupNameModify bool `mapstructure:"ldap-group-name-modify" json:"ldapGroupNameModify"` + LdapUserNameModify bool `mapstructure:"ldap-user-name-modify" json:"ldapUserNameModify"` } type EmailConfig struct { Host string `mapstructure:"host" json:"host"` diff --git a/controller/group_controller.go b/controller/group_controller.go index aab4eda..126a2f9 100644 --- a/controller/group_controller.go +++ b/controller/group_controller.go @@ -33,13 +33,13 @@ func (m *GroupController) UserNoInGroup(c *gin.Context) { }) } -// // GetTree 接口树 -// func (m *GroupController) GetTree(c *gin.Context) { -// req := new(request.GroupGetTreeReq) -// Run(c, req, func() (interface{}, interface{}) { -// return logic.Group.GetTree(c, req) -// }) -// } +// GetTree 接口树 +func (m *GroupController) GetTree(c *gin.Context) { + req := new(request.GroupGetTreeReq) + Run(c, req, func() (interface{}, interface{}) { + return logic.Group.GetTree(c, req) + }) +} // Add 新建记录 func (m *GroupController) Add(c *gin.Context) { diff --git a/docs/docker/config.yml b/docs/docker/config.yml index 9f244b3..42e60e7 100644 --- a/docs/docker/config.yml +++ b/docs/docker/config.yml @@ -89,4 +89,6 @@ ldap: ldap-admin-dn: "cn=admin,dc=eryajf,dc=net" ldap-admin-pass: "123456" ldap-user-dn: "ou=people,dc=eryajf,dc=net" - ldap-group-dn: "ou=group,dc=eryajf,dc=net" \ No newline at end of file + ldap-group-dn: "ou=group,dc=eryajf,dc=net" + ldap-group-name-modify: false + ldap-user-name-modify: false \ No newline at end of file diff --git a/go.mod b/go.mod index f3e5e0e..ee8c350 100644 --- a/go.mod +++ b/go.mod @@ -24,41 +24,19 @@ require ( ) require ( - github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect - github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/denisenkom/go-mssqldb v0.9.0 // indirect - github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.1 // indirect - github.com/go-sql-driver/mysql v1.5.0 // indirect - github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/protobuf v1.4.3 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.8.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.0.7 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.6.2 // indirect - github.com/jackc/pgx/v4 v4.10.1 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.1 // indirect github.com/json-iterator/go v1.1.10 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.4 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pelletier/go-toml v1.8.1 // indirect github.com/spf13/afero v1.5.1 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect - github.com/ugorji/go/codec v1.2.3 // indirect - go.uber.org/atomic v1.7.0 // indirect + github.com/ugorji/go v1.2.3 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect diff --git a/logic/group_logic.go b/logic/group_logic.go index a7e8e54..4a96563 100644 --- a/logic/group_logic.go +++ b/logic/group_logic.go @@ -2,6 +2,7 @@ package logic import ( "fmt" + "github.com/eryajf-world/go-ldap-admin/config" "strings" "github.com/eryajf-world/go-ldap-admin/model" @@ -35,12 +36,20 @@ func (l GroupLogic) Add(c *gin.Context, req interface{}) (data interface{}, rspE } group := model.Group{ + GroupType: r.GroupType, + ParentId: r.ParentId, GroupName: r.GroupName, Remark: r.Remark, Creator: ctxUser.Username, } - - err = ildap.Group.Add(&group) + pdn := "" + if group.ParentId > 0 { + pdn, err = isql.Group.GetGroupDn(r.ParentId, "") + if err != nil { + return nil, err.Error() + } + } + err = ildap.Group.Add(&group, pdn) if err != nil { return nil, tools.NewLdapError(fmt.Errorf("向LDAP创建分组失败" + err.Error())) } @@ -95,6 +104,29 @@ func (l GroupLogic) List(c *gin.Context, req interface{}) (data interface{}, rsp }, nil } +// GetTree 数据树 +func (l GroupLogic) GetTree(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { + r, ok := req.(*request.GroupGetTreeReq) + if !ok { + return nil, ReqAssertErr + } + _ = c + rList := request.GroupListReq{} + rList.PageNum = r.PageNum + rList.PageSize = r.PageSize + rList.GroupName = r.GroupName + rList.Remark = r.Remark + var groups []*model.Group + groups, err := isql.Group.List(&rList) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("获取资源列表失败: " + err.Error())) + } + + tree := isql.GenGroupTree(0, groups) + + return tree, nil +} + // Update 更新数据 func (l GroupLogic) Update(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { r, ok := req.(*request.GroupUpdateReq) @@ -122,15 +154,26 @@ func (l GroupLogic) Update(c *gin.Context, req interface{}) (data interface{}, r group := model.Group{ Model: oldData.Model, - GroupName: oldData.GroupName, + GroupName: r.GroupName, Remark: r.Remark, Creator: ctxUser.Username, - } - err = ildap.Group.Update(&group) - if err != nil { - return nil, tools.NewLdapError(fmt.Errorf("向LDAP更新分组失败")) + GroupType: oldData.GroupType, } + oldGroupName := oldData.GroupName + oldRemark := oldData.Remark + dn, err := isql.Group.GetGroupDn(r.ID, "") + if err != nil { + return nil, err.Error() + } + err = ildap.Group.Update(&group, dn, oldGroupName, oldRemark) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("向LDAP更新分组失败:" + err.Error())) + } + //若配置了不允许修改分组名称,则不更新分组名称 + if !config.Conf.Ldap.LdapGroupNameModify { + group.GroupName = oldGroupName + } err = isql.Group.Update(&group) if err != nil { return nil, tools.NewLdapError(fmt.Errorf("向MySQL更新分组失败")) @@ -158,9 +201,19 @@ func (l GroupLogic) Delete(c *gin.Context, req interface{}) (data interface{}, r return nil, tools.NewMySqlError(fmt.Errorf("获取分组列表失败: %s", err.Error())) } for _, group := range groups { - err := ildap.Group.Delete(group.GroupName) + // 判断存在子分组,不允许删除 + filter := tools.H{"parent_id": int(group.ID)} + if isql.Group.Exist(filter) { + return nil, tools.NewMySqlError(fmt.Errorf("存在子分组,不允许删除!")) + } + dn, err := isql.Group.GetGroupDn(group.ID, "") if err != nil { - return nil, tools.NewLdapError(fmt.Errorf("向LDAP删除分组失败")) + return nil, err.Error() + } + gdn := fmt.Sprintf("%s,%s", dn, config.Conf.Ldap.LdapBaseDN) + err = ildap.Group.Delete(gdn) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("向LDAP删除分组失败:" + err.Error())) } } // 删除接口 @@ -168,6 +221,7 @@ func (l GroupLogic) Delete(c *gin.Context, req interface{}) (data interface{}, r if err != nil { return nil, tools.NewMySqlError(fmt.Errorf("删除接口失败: %s", err.Error())) } + // TODO: 删除用户组关系 return nil, nil } @@ -197,7 +251,16 @@ func (l GroupLogic) AddUser(c *gin.Context, req interface{}) (data interface{}, } for _, user := range users { - err := ildap.Group.AddUserToGroup(group.GroupName, user.Username) + gdn, err := isql.Group.GetGroupDn(group.ID, "") + if err != nil { + return nil, err.Error() + } + gdn = fmt.Sprintf("%s,%s", gdn, config.Conf.Ldap.LdapBaseDN) + udn := config.Conf.Ldap.LdapAdminDN + if user.Username != "admin" { + udn = fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN) + } + err = ildap.Group.AddUserToGroup(gdn, udn) if err != nil { return nil, tools.NewLdapError(fmt.Errorf("向LDAP添加用户到分组失败" + err.Error())) @@ -236,14 +299,17 @@ func (l GroupLogic) RemoveUser(c *gin.Context, req interface{}) (data interface{ if err != nil { return nil, tools.NewMySqlError(fmt.Errorf("获取分组失败: %s", err.Error())) } - + gdn, err := isql.Group.GetGroupDn(r.GroupID, "") + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("获取分组失败: %s", err.Error())) + } + gdn = fmt.Sprintf("%s,%s", gdn, config.Conf.Ldap.LdapBaseDN) for _, user := range users { - err := ildap.Group.RemoveUserFromGroup(group.GroupName, user.Username) + err := ildap.Group.RemoveUserFromGroup(gdn, user.Username) if err != nil { return nil, tools.NewLdapError(fmt.Errorf("将用户从ldap移除失败" + err.Error())) } } - err = isql.Group.RemoveUserFromGroup(group, users) if err != nil { return nil, tools.NewMySqlError(fmt.Errorf("将用户从MySQL移除失败: %s", err.Error())) diff --git a/logic/user_logic.go b/logic/user_logic.go index fec4d1c..5efb018 100644 --- a/logic/user_logic.go +++ b/logic/user_logic.go @@ -1,6 +1,7 @@ package logic import ( + "errors" "fmt" "github.com/eryajf-world/go-ldap-admin/config" @@ -96,17 +97,48 @@ func (l UserLogic) Add(c *gin.Context, req interface{}) (data interface{}, rspEr Introduction: r.Introduction, Status: r.Status, Creator: ctxUser.Username, + DepartmentId: r.DepartmentId, + Source: r.Source, Roles: roles, } - + if user.Source == "" { + user.Source = "platform" + } + //先识别用户选择的部门是否是OU开头 + dn, err := isql.Group.GetGroupDn(r.DepartmentId, "") + if err != nil { + return nil, err.Error() + } + gdn := fmt.Sprintf("%s,%s", dn, config.Conf.Ldap.LdapBaseDN) + if gdn[:3] == "ou=" { + return nil, errors.New("不能添加用户到OU组织单元") + } + //先创建用户到默认分组 err = ildap.User.Add(&user) if err != nil { - return nil, tools.NewLdapError(fmt.Errorf("向LDAP创建用户失败" + err.Error())) + return nil, tools.NewLdapError(fmt.Errorf("向LDAP创建用户失败:" + err.Error())) + } + //根据选择的部门,添加到部门内 + err = ildap.Group.AddUserToGroup(gdn, fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN)) + if err != nil { + return nil, err.Error() } - err = isql.User.Add(&user) if err != nil { - return nil, tools.NewMySqlError(fmt.Errorf("向MySQL创建用户失败")) + return nil, tools.NewMySqlError(fmt.Errorf("向MySQL创建用户失败:" + err.Error())) + } + //根据部门分配,将用户和部门信息维护到部门关系表里面 + users := []model.User{} + users = append(users, user) + depart := new(model.Group) + filter := tools.H{"id": int(r.DepartmentId)} + err = isql.Group.Find(filter, depart) + if err != nil { + return "", tools.NewMySqlError(err) + } + err = isql.Group.AddUserToGroup(depart, users) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("向MySQL添加用户到分组关系失败:" + err.Error())) } return nil, nil } @@ -121,16 +153,16 @@ func (l UserLogic) List(c *gin.Context, req interface{}) (data interface{}, rspE users, err := isql.User.List(r) if err != nil { - return nil, tools.NewMySqlError(fmt.Errorf("获取用户列表失败")) + return nil, tools.NewMySqlError(fmt.Errorf("获取用户列表失败:" + err.Error())) } rets := make([]model.User, 0) for _, user := range users { rets = append(rets, *user) } - count, err := isql.User.Count() + count, err := isql.User.ListCount(r) if err != nil { - return nil, tools.NewMySqlError(fmt.Errorf("获取用户总数失败")) + return nil, tools.NewMySqlError(fmt.Errorf("获取用户总数失败:" + err.Error())) } return response.UserListRsp{ @@ -194,6 +226,7 @@ func (l UserLogic) Update(c *gin.Context, req interface{}) (data interface{}, rs user := model.User{ Model: oldData.Model, + Username: r.Username, Nickname: r.Nickname, GivenName: r.GivenName, Mail: r.Mail, @@ -205,6 +238,8 @@ func (l UserLogic) Update(c *gin.Context, req interface{}) (data interface{}, rs Position: r.Position, Introduction: r.Introduction, Creator: ctxUser.Username, + DepartmentId: r.DepartmentId, + Source: oldData.Source, Roles: roles, } @@ -230,19 +265,108 @@ func (l UserLogic) Update(c *gin.Context, req interface{}) (data interface{}, rs return nil, tools.NewValidatorError(fmt.Errorf("用户不能把别的用户角色等级更新得比自己高或相等")) } } - err = ildap.User.Update(oldData.Username, &user) if err != nil { - return nil, tools.NewLdapError(fmt.Errorf("在LDAP更新用户失败" + err.Error())) + return nil, tools.NewLdapError(fmt.Errorf("在LDAP更新用户失败:" + err.Error())) } // 更新用户 + if !config.Conf.Ldap.LdapUserNameModify { + user.Username = oldData.Username + } err = isql.User.Update(&user) if err != nil { - return nil, tools.NewMySqlError(fmt.Errorf("在MySQL更新用户失败" + err.Error())) + return nil, tools.NewMySqlError(fmt.Errorf("在MySQL更新用户失败:" + err.Error())) + } + //判断部门信息是否有变化有变化则更新相应的数据库 + if oldData.DepartmentId != r.DepartmentId { + //从旧组中删除 + err = l.RemoveUserToGroup(oldData.DepartmentId, []uint{r.ID}) + //添加到新分组中 + err = l.AddUserToGroup(r.DepartmentId, []uint{r.ID}) + if err != nil { + return nil, err + } + } + return nil, nil +} + +// RemoveUser 移除用户 +func (l UserLogic) RemoveUserToGroup(groupId uint, userIds []uint) error { + filter := tools.H{"id": groupId} + + if !isql.Group.Exist(filter) { + return tools.NewMySqlError(fmt.Errorf("分组不存在")) } - return nil, nil + users, err := isql.User.GetUserByIds(userIds) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("获取用户列表失败: %s", err.Error())) + } + + group := new(model.Group) + err = isql.Group.Find(filter, group) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("获取分组失败: %s", err.Error())) + } + gdn, err := isql.Group.GetGroupDn(groupId, "") + if err != nil { + return tools.NewMySqlError(fmt.Errorf("获取分组失败: %s", err.Error())) + } + gdn = fmt.Sprintf("%s,%s", gdn, config.Conf.Ldap.LdapBaseDN) + for _, user := range users { + err := ildap.Group.RemoveUserFromGroup(gdn, user.Username) + if err != nil { + return tools.NewLdapError(fmt.Errorf("将用户从ldap移除失败" + err.Error())) + } + } + err = isql.Group.RemoveUserFromGroup(group, users) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("将用户从MySQL移除失败: %s", err.Error())) + } + + return nil +} + +//将用户添加到分组中 +func (l UserLogic) AddUserToGroup(groupId uint, userIds []uint) error { + filter := tools.H{"id": groupId} + + if !isql.Group.Exist(filter) { + return tools.NewMySqlError(fmt.Errorf("分组不存在")) + } + + users, err := isql.User.GetUserByIds(userIds) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("获取用户列表失败: %s", err.Error())) + } + + group := new(model.Group) + err = isql.Group.Find(filter, group) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("获取分组失败: %s", err.Error())) + } + for _, user := range users { + gdn, err := isql.Group.GetGroupDn(group.ID, "") + if err != nil { + return err + } + gdn = fmt.Sprintf("%s,%s", gdn, config.Conf.Ldap.LdapBaseDN) + udn := config.Conf.Ldap.LdapAdminDN + if user.Username != "admin" { + udn = fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN) + } + err = ildap.Group.AddUserToGroup(gdn, udn) + + if err != nil { + return tools.NewLdapError(fmt.Errorf("向LDAP添加用户到分组失败" + err.Error())) + } + } + err = isql.Group.AddUserToGroup(group, users) + if err != nil { + return tools.NewMySqlError(fmt.Errorf("添加用户到分组失败: %s", err.Error())) + } + return nil } // Delete 删除数据 @@ -388,7 +512,7 @@ func (l UserLogic) ChangeUserStatus(c *gin.Context, req interface{}) (data inter return nil, tools.NewLdapError(fmt.Errorf("在LDAP删除用户失败" + err.Error())) } } else { - err := ildap.User.Add(user) + err = ildap.User.Add(user) if err != nil { return nil, tools.NewLdapError(fmt.Errorf("在LDAP添加用户失败" + err.Error())) } diff --git a/model/group.go b/model/group.go index b501873..473e5e6 100644 --- a/model/group.go +++ b/model/group.go @@ -4,8 +4,11 @@ import "gorm.io/gorm" type Group struct { gorm.Model - GroupName string `gorm:"type:varchar(20);comment:'分组名称'" json:"groupName"` - Remark string `gorm:"type:varchar(100);comment:'分组中文说明'" json:"remark"` - Creator string `gorm:"type:varchar(20);comment:'创建人'" json:"creator"` - Users []*User `gorm:"many2many:group_users" json:"users"` + GroupName string `gorm:"type:varchar(20);comment:'分组名称'" json:"groupName"` + Remark string `gorm:"type:varchar(100);comment:'分组中文说明'" json:"remark"` + Creator string `gorm:"type:varchar(20);comment:'创建人'" json:"creator"` + GroupType string `gorm:"type:varchar(20);comment:'分组类型:cn、ou'" json:"groupType"` + Users []*User `gorm:"many2many:group_users" json:"users"` + ParentId uint `gorm:"default:0;comment:'父组编号(编号为0时表示根组)'" json:"parentId"` + Children []*Group `gorm:"-" json:"children"` } diff --git a/model/user.go b/model/user.go index 5bac54a..125825d 100644 --- a/model/user.go +++ b/model/user.go @@ -4,19 +4,21 @@ import "gorm.io/gorm" type User struct { gorm.Model - Username string `gorm:"type:varchar(10);not null;unique" json:"username"` // 用户名 - Password string `gorm:"size:255;not null" json:"password"` // 用户密码 - Nickname string `gorm:"type:varchar(10)" json:"nickname"` // 昵称 - GivenName string `gorm:"type:varchar(10)" json:"givenName"` // 花名,如果有的话,没有的话用昵称占位 - Mail string `gorm:"type:varchar(20)" json:"mail"` // 邮箱 - JobNumber string `gorm:"type:varchar(5)" json:"jobNumber"` // 工号 - Mobile string `gorm:"type:varchar(11);not null;unique" json:"mobile"` // 手机号 - Avatar string `gorm:"type:varchar(255)" json:"avatar"` // 头像 - PostalAddress string `gorm:"type:varchar(255)" json:"postalAddress"` // 地址 - Departments string `gorm:"type:varchar(128)" json:"departments"` // 部门 - Position string `gorm:"type:varchar(128)" json:"position"` // 职位 - Introduction string `gorm:"type:varchar(255)" json:"introduction"` // 个人简介 - Status uint `gorm:"type:tinyint(1);default:1;comment:'1在职, 2离职'" json:"status"` // 状态 - Creator string `gorm:"type:varchar(20);" json:"creator"` // 创建者 - Roles []*Role `gorm:"many2many:user_roles" json:"roles"` // 角色 + Username string `gorm:"type:varchar(50);not null;unique;comment:'用户名'" json:"username"` // 用户名 + Password string `gorm:"size:255;not null;comment:'用户密码'" json:"password"` // 用户密码 + Nickname string `gorm:"type:varchar(50);comment:'中文名'" json:"nickname"` // 昵称 + GivenName string `gorm:"type:varchar(50);comment:'花名'" json:"givenName"` // 花名,如果有的话,没有的话用昵称占位 + Mail string `gorm:"type:varchar(20);comment:'邮箱'" json:"mail"` // 邮箱 + JobNumber string `gorm:"type:varchar(20);comment:'工号'" json:"jobNumber"` // 工号 + Mobile string `gorm:"type:varchar(11);not null;unique;comment:'手机号'" json:"mobile"` // 手机号 + Avatar string `gorm:"type:varchar(255);comment:'头像'" json:"avatar"` // 头像 + PostalAddress string `gorm:"type:varchar(255);comment:'地址'" json:"postalAddress"` // 地址 + Departments string `gorm:"type:varchar(128);comment:'部门'" json:"departments"` // 部门 + Position string `gorm:"type:varchar(128);comment:'职位'" json:"position"` // 职位 + Introduction string `gorm:"type:varchar(255);comment:'个人简介'" json:"introduction"` // 个人简介 + Status uint `gorm:"type:tinyint(1);default:1;comment:'状态:1在职, 2离职'" json:"status"` // 状态 + Creator string `gorm:"type:varchar(20);;comment:'创建者'" json:"creator"` // 创建者 + Source string `gorm:"type:varchar(50);comment:'用户来源:dingTalk、weCom、ldap、platform'" json:"source"` // 来源 + DepartmentId uint `gorm:"type:int(20);not null;comment:'部门id'" json:"departmentId"` // 部门id + Roles []*Role `gorm:"many2many:user_roles" json:"roles"` // 角色 } diff --git a/public/common/init_mysql_data.go b/public/common/init_mysql_data.go index b9dcba6..3bdf046 100644 --- a/public/common/init_mysql_data.go +++ b/public/common/init_mysql_data.go @@ -331,6 +331,13 @@ func InitData() { Remark: "获取分组列表", Creator: "系统", }, + { + Method: "GET", + Path: "/group/tree", + Category: "group", + Remark: "获取分组列表树", + Creator: "系统", + }, { Method: "POST", Path: "/group/add", @@ -549,6 +556,7 @@ func InitData() { "/user/list", "/user/changePwd", "/group/list", + "/group/tree", "/group/useringroup", "/group/usernoingroup", "/role/list", diff --git a/public/tools/web.go b/public/tools/web.go index a3bbda2..51a260e 100644 --- a/public/tools/web.go +++ b/public/tools/web.go @@ -17,7 +17,7 @@ func init() { // NewPageOption 创建一个分页参数 func NewPageOption(pageNum, pageSize int) *PageOption { - if !(pageSize > 0 && pageSize < 1000) || pageNum < 0 || pageSize <= 0 { + if !(pageSize > 0 && pageSize <= 1000) || pageNum < 0 || pageSize <= 0 { return defaultOptions } diff --git a/routes/group_routes.go b/routes/group_routes.go index 7289e69..b9a0322 100644 --- a/routes/group_routes.go +++ b/routes/group_routes.go @@ -16,6 +16,7 @@ func InitGroupRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) g group.Use(middleware.CasbinMiddleware()) { group.GET("/list", controller.Group.List) + group.GET("/tree", controller.Group.GetTree) group.POST("/add", controller.Group.Add) group.POST("/update", controller.Group.Update) group.POST("/delete", controller.Group.Delete) diff --git a/service/ildap/group_ildap.go b/service/ildap/group_ildap.go index 08ef95f..4f17844 100644 --- a/service/ildap/group_ildap.go +++ b/service/ildap/group_ildap.go @@ -1,8 +1,8 @@ package ildap import ( + "errors" "fmt" - "github.com/eryajf-world/go-ldap-admin/config" "github.com/eryajf-world/go-ldap-admin/model" "github.com/eryajf-world/go-ldap-admin/public/common" @@ -13,46 +13,78 @@ import ( type GroupService struct{} // Add 添加资源 -func (x GroupService) Add(g *model.Group) error { - add := ldap.NewAddRequest(fmt.Sprintf("cn=%s,%s", g.GroupName, config.Conf.Ldap.LdapGroupDN), nil) - add.Attribute("objectClass", []string{"groupOfNames", "top"}) // 如果定义了 groupOfNAmes,那么必须指定member,否则报错如下:object class 'groupOfNames' requires attribute 'member' - add.Attribute("cn", []string{g.GroupName}) +func (x GroupService) Add(g *model.Group, pdn string) error { //organizationalUnit + parentDn := config.Conf.Ldap.LdapBaseDN + if pdn != "" { + parentDn = fmt.Sprintf("%s,%s", pdn, config.Conf.Ldap.LdapBaseDN) + } + dn := fmt.Sprintf("%s=%s,%s", g.GroupType, g.GroupName, parentDn) + add := ldap.NewAddRequest(dn, nil) + if g.GroupType == "ou" { + add.Attribute("objectClass", []string{"organizationalUnit", "top"}) // 如果定义了 groupOfNAmes,那么必须指定member,否则报错如下:object class 'groupOfNames' requires attribute 'member' + } + if g.GroupType == "cn" { + add.Attribute("objectClass", []string{"groupOfUniqueNames", "top"}) + add.Attribute("uniqueMember", []string{config.Conf.Ldap.LdapAdminDN}) // 所以这里创建组的时候,默认将admin加入其中,以免创建时没有人员而报上边的错误 + } + add.Attribute(g.GroupType, []string{g.GroupName}) add.Attribute("description", []string{g.Remark}) - add.Attribute("member", []string{config.Conf.Ldap.LdapAdminDN}) // 所以这里创建组的时候,默认将admin加入其中,以免创建时没有人员而报上边的错误 return common.LDAP.Add(add) } // UpdateGroup 更新一个分组 -func (x GroupService) Update(g *model.Group) error { - modify := ldap.NewModifyRequest(fmt.Sprintf("cn=%s,%s", g.GroupName, config.Conf.Ldap.LdapGroupDN), nil) - modify.Replace("description", []string{g.Remark}) - return common.LDAP.Modify(modify) +func (x GroupService) Update(g *model.Group, pdn string, oldGroupName, oldRemark string) error { + parentDn := "," + config.Conf.Ldap.LdapBaseDN + if pdn != "" { + parentDn = fmt.Sprintf("%s,%s", pdn, config.Conf.Ldap.LdapBaseDN) + } + //默认更新remark字段 + if g.Remark != oldRemark { + modify := ldap.NewModifyRequest(parentDn, nil) + modify.Replace("description", []string{g.Remark}) + err := common.LDAP.Modify(modify) + if err != nil { + return err + } + } + // 如果配置文件允许修改分组名称,且分组名称发生了变化,那么执行修改分组名称 + if config.Conf.Ldap.LdapGroupNameModify && g.GroupName != oldGroupName { + rdn := fmt.Sprintf("%s=%s", g.GroupType, g.GroupName) + modify := ldap.NewModifyDNRequest(parentDn, rdn, true, "") + err := common.LDAP.ModifyDN(modify) + if err != nil { + return err + } + } + return nil } // Delete 删除资源 -func (x GroupService) Delete(group string) error { - del := ldap.NewDelRequest(fmt.Sprintf("cn=%s,%s", group, config.Conf.Ldap.LdapGroupDN), nil) +func (x GroupService) Delete(pdn string) error { + parentDn := "," + config.Conf.Ldap.LdapBaseDN + if pdn != "" { + parentDn = fmt.Sprintf("%s,%s", pdn, config.Conf.Ldap.LdapBaseDN) + } + del := ldap.NewDelRequest(parentDn, nil) return common.LDAP.Del(del) } // AddUserToGroup 添加用户到分组 -func (x GroupService) AddUserToGroup(group, user string) error { - udn := fmt.Sprintf("uid=%s,%s", user, config.Conf.Ldap.LdapUserDN) - if user == "admin" { - udn = config.Conf.Ldap.LdapAdminDN +func (x GroupService) AddUserToGroup(dn, udn string) error { + //判断dn是否以ou开头 + if dn[:3] == "ou=" { + return errors.New("不能添加用户到OU组织单元") } - gdn := fmt.Sprintf("cn=%s,%s", group, config.Conf.Ldap.LdapGroupDN) - newmr := ldap.NewModifyRequest(gdn, nil) - newmr.Add("member", []string{udn}) + newmr := ldap.NewModifyRequest(dn, nil) + newmr.Add("uniqueMember", []string{udn}) return common.LDAP.Modify(newmr) } // DelUserFromGroup 将用户从分组删除 -func (x GroupService) RemoveUserFromGroup(group, user string) error { +func (x GroupService) RemoveUserFromGroup(gdn, user string) error { udn := fmt.Sprintf("uid=%s,%s", user, config.Conf.Ldap.LdapUserDN) - gdn := fmt.Sprintf("cn=%s,%s", group, config.Conf.Ldap.LdapGroupDN) newmr := ldap.NewModifyRequest(gdn, nil) - newmr.Delete("member", []string{udn}) + newmr.Delete("uniqueMember", []string{udn}) return common.LDAP.Modify(newmr) } diff --git a/service/ildap/user_ildap.go b/service/ildap/user_ildap.go index 1f1f65b..360f590 100644 --- a/service/ildap/user_ildap.go +++ b/service/ildap/user_ildap.go @@ -29,7 +29,6 @@ func (x UserService) Add(user *model.User) error { if user.Introduction == "" { user.Introduction = user.Nickname } - add := ldap.NewAddRequest(fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN), nil) add.Attribute("objectClass", []string{"inetOrgPerson"}) add.Attribute("cn", []string{user.Nickname}) @@ -62,8 +61,15 @@ func (x UserService) Update(oldusername string, user *model.User) error { modify.Replace("givenName", []string{user.GivenName}) modify.Replace("postalAddress", []string{user.PostalAddress}) modify.Replace("mobile", []string{user.Mobile}) - modify.Replace("uid", []string{oldusername}) - return common.LDAP.Modify(modify) + err := common.LDAP.Modify(modify) + if err != nil { + return err + } + if config.Conf.Ldap.LdapUserNameModify && oldusername != user.Username { + modifyDn := ldap.NewModifyDNRequest(fmt.Sprintf("uid=%s,%s", oldusername, config.Conf.Ldap.LdapUserDN), fmt.Sprintf("uid=%s", user.Username), true, "") + return common.LDAP.ModifyDN(modifyDn) + } + return nil } // Delete 删除资源 diff --git a/service/isql/group_isql.go b/service/isql/group_isql.go index 22955e5..401f76d 100644 --- a/service/isql/group_isql.go +++ b/service/isql/group_isql.go @@ -34,6 +34,44 @@ func (s GroupService) List(req *request.GroupListReq) ([]*model.Group, error) { return list, err } +// 拼装dn信息 +func (s GroupService) GetGroupDn(groupId uint, oldDn string) (dn string, e error) { + depart := new(model.Group) + filter := tools.H{"id": int(groupId)} + err := s.Find(filter, depart) + if err != nil { + return "", tools.NewMySqlError(err) + } + if oldDn == "" { + dn = fmt.Sprintf("%s=%s", depart.GroupType, depart.GroupName) + } else { + dn = fmt.Sprintf("%s,%s=%s", oldDn, depart.GroupType, depart.GroupName) + } + if depart.ParentId > 0 { + tempDn, err := s.GetGroupDn(depart.ParentId, dn) + if err != nil { + return dn, err + } + dn = tempDn + fmt.Println(tempDn) + } + return dn, nil +} + +// GenGroupTree 生成分组树 +func GenGroupTree(parentId uint, groups []*model.Group) []*model.Group { + tree := make([]*model.Group, 0) + + for _, g := range groups { + if g.ParentId == parentId { + children := GenGroupTree(g.ID, groups) + g.Children = children + tree = append(tree, g) + } + } + return tree +} + // Count 获取数据总数 func (s GroupService) Count() (int64, error) { var count int64 diff --git a/service/isql/user_isql.go b/service/isql/user_isql.go index eac7603..b2f52fb 100644 --- a/service/isql/user_isql.go +++ b/service/isql/user_isql.go @@ -25,6 +25,8 @@ var userInfoCache = cache.New(24*time.Hour, 48*time.Hour) // Add 添加资源 func (s UserService) Add(user *model.User) error { user.Password = tools.NewGenPasswd(user.Password) + //result := common.DB.Create(user) + //return user.ID, result.Error return common.DB.Create(user).Error } @@ -45,6 +47,14 @@ func (s UserService) List(req *request.UserListReq) ([]*model.User, error) { if mobile != "" { db = db.Where("mobile LIKE ?", fmt.Sprintf("%%%s%%", mobile)) } + departmentId := req.DepartmentId + if departmentId > 0 { + db = db.Where("department_id = ?", departmentId) + } + givenName := strings.TrimSpace(req.GivenName) + if givenName != "" { + db = db.Where("given_name LIKE ?", fmt.Sprintf("%%%s%%", givenName)) + } status := req.Status if status != 0 { db = db.Where("status = ?", status) @@ -55,6 +65,40 @@ func (s UserService) List(req *request.UserListReq) ([]*model.User, error) { return list, err } +// ListCout 获取符合条件的数据列表条数 +func (s UserService) ListCount(req *request.UserListReq) (int64, error) { + var count int64 + db := common.DB.Model(&model.User{}).Order("id DESC") + + username := strings.TrimSpace(req.Username) + if username != "" { + db = db.Where("username LIKE ?", fmt.Sprintf("%%%s%%", username)) + } + nickname := strings.TrimSpace(req.Nickname) + if nickname != "" { + db = db.Where("nickname LIKE ?", fmt.Sprintf("%%%s%%", nickname)) + } + mobile := strings.TrimSpace(req.Mobile) + if mobile != "" { + db = db.Where("mobile LIKE ?", fmt.Sprintf("%%%s%%", mobile)) + } + departmentId := req.DepartmentId + if departmentId > 0 { + db = db.Where("department_id = ?", departmentId) + } + givenName := strings.TrimSpace(req.GivenName) + if givenName != "" { + db = db.Where("given_name LIKE ?", fmt.Sprintf("%%%s%%", givenName)) + } + status := req.Status + if status != 0 { + db = db.Where("status = ?", status) + } + + err := db.Count(&count).Error + return count, err +} + // List 获取数据列表 func (s UserService) ListAll() (list []*model.User, err error) { err = common.DB.Model(&model.User{}).Order("created_at DESC").Find(&list).Error diff --git a/svc/request/group_req.go b/svc/request/group_req.go index 6ec9a0d..7a3c519 100644 --- a/svc/request/group_req.go +++ b/svc/request/group_req.go @@ -10,14 +10,18 @@ type GroupListReq struct { // GroupAddReq 添加资源结构体 type GroupAddReq struct { + GroupType string `json:"groupType" validate:"required,min=1,max=20"` GroupName string `json:"groupName" validate:"required,min=1,max=20"` - Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 + //父级Id 大于等于0 必填 + ParentId uint `json:"parentId" validate:"omitempty,min=0"` + Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 } // GroupUpdateReq 更新资源结构体 type GroupUpdateReq struct { - ID uint `json:"id" form:"id" validate:"required"` - Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 + ID uint `json:"id" form:"id" validate:"required"` + GroupName string `json:"groupName" validate:"required,min=1,max=20"` + Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 } // GroupDeleteReq 删除资源结构体 @@ -27,6 +31,10 @@ type GroupDeleteReq struct { // GroupGetTreeReq 获取资源树结构体 type GroupGetTreeReq struct { + GroupName string `json:"groupName" form:"groupName"` + Remark string `json:"remark" form:"remark"` + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` } type GroupAddUserReq struct { diff --git a/svc/request/user_req.go b/svc/request/user_req.go index 08a4634..bdda04e 100644 --- a/svc/request/user_req.go +++ b/svc/request/user_req.go @@ -15,12 +15,15 @@ type UserAddReq struct { Avatar string `json:"avatar"` Introduction string `json:"introduction" validate:"min=0,max=255"` Status uint `json:"status" validate:"oneof=1 2"` + DepartmentId uint `json:"departmentId" validate:"required"` + Source string `json:"source" validate:"min=0,max=20"` RoleIds []uint `json:"roleIds" validate:"required"` } // UserUpdateReq 更新资源结构体 type UserUpdateReq struct { ID uint `json:"id" validate:"required"` + Username string `json:"username" validate:"required,min=2,max=20"` Nickname string `json:"nickname" validate:"min=0,max=20"` GivenName string `json:"givenName" validate:"min=0,max=20"` Mail string `json:"mail" validate:"min=0,max=20"` @@ -31,6 +34,8 @@ type UserUpdateReq struct { Mobile string `json:"mobile" validate:"checkMobile"` Avatar string `json:"avatar"` Introduction string `json:"introduction" validate:"min=0,max=255"` + DepartmentId uint `json:"departmentId" validate:"required"` + Source string `json:"source" validate:"min=0,max=20"` RoleIds []uint `json:"roleIds" validate:"required"` } @@ -57,12 +62,14 @@ type UserGetUserInfoReq struct { // UserListReq 获取用户列表结构体 type UserListReq struct { - Username string `json:"username" form:"username"` - Mobile string `json:"mobile" form:"mobile" ` - Nickname string `json:"nickname" form:"nickname"` - Status uint `json:"status" form:"status" ` - PageNum int `json:"pageNum" form:"pageNum"` - PageSize int `json:"pageSize" form:"pageSize"` + Username string `json:"username" form:"username"` + Mobile string `json:"mobile" form:"mobile" ` + Nickname string `json:"nickname" form:"nickname"` + GivenName string `json:"givenName" form:"givenName"` + DepartmentId uint `json:"departmentId" form:"departmentId"` + Status uint `json:"status" form:"status" ` + PageNum int `json:"pageNum" form:"pageNum"` + PageSize int `json:"pageSize" form:"pageSize"` } // RegisterAndLoginReq 用户登录结构体