Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 支持一键导入已经在运行openldap数据的能力 #60

Merged
merged 12 commits into from
Jul 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions controller/group_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,11 @@ func (m *GroupController) SyncFeiShuDepts(c *gin.Context) {
return logic.FeiShu.SyncFeiShuDepts(c, req)
})
}

//同步原ldap部门信息
func (m *GroupController) SyncOpenLdapDepts(c *gin.Context) {
req := new(request.SyncOpenLdapDeptsReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.OpenLdap.SyncOpenLdapDepts(c, req)
})
}
8 changes: 8 additions & 0 deletions controller/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,11 @@ func (uc UserController) SyncFeiShuUsers(c *gin.Context) {
return logic.FeiShu.SyncFeiShuUsers(c, req)
})
}

// 同步ldap用户信息
func (uc UserController) SyncOpenLdapUsers(c *gin.Context) {
req := new(request.SyncOpenLdapUserReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.OpenLdap.SyncOpenLdapUsers(c, req)
})
}
1 change: 1 addition & 0 deletions logic/a_logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
DingTalk = &DingTalkLogic{}
WeCom = &WeComLogic{}
FeiShu = &FeiShuLogic{}
OpenLdap = &OpenLdapLogic{}
Base = &BaseLogic{}
FieldRelation = &FieldRelationLogic{}

Expand Down
179 changes: 179 additions & 0 deletions logic/openldap_logic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package logic

import (
"fmt"

"github.com/eryajf/go-ldap-admin/model"
"github.com/eryajf/go-ldap-admin/public/client/openldap"

"github.com/eryajf/go-ldap-admin/public/tools"
"github.com/eryajf/go-ldap-admin/service/isql"
"github.com/gin-gonic/gin"
)

type OpenLdapLogic struct {
}

//通过ldap获取部门信息
func (d *OpenLdapLogic) SyncOpenLdapDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取所有部门
depts, err := openldap.GetAllDepts()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("获取ldap部门列表失败:%s", err.Error()))
}
// 2.将部门这个数组进行拆分,一组是父ID为根的,一组是父ID不为根的
var firstDepts []*openldap.Dept // 父ID为根的部门
var otherDepts []*openldap.Dept // 父ID不为根的部门
for _, dept := range depts {
if dept.ParentId == "1" {
firstDepts = append(firstDepts, dept)
} else {
otherDepts = append(otherDepts, dept)
}
}
// 3.先写父ID为根的,再写父ID不为根的
for _, dept := range firstDepts {
err := d.AddDepts(&model.Group{
GroupName: dept.Name,
Remark: dept.Remark,
Creator: "system",
GroupType: "cn",
SourceDeptId: dept.Id,
Source: "openldap",
SourceDeptParentId: dept.ParentId,
GroupDN: dept.DN,
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncOpenLdapDepts添加根部门失败:%s", err.Error()))
}
}

for _, dept := range otherDepts {
err := d.AddDepts(&model.Group{
GroupName: dept.Name,
Remark: dept.Remark,
Creator: "system",
GroupType: "cn",
SourceDeptId: dept.Id,
Source: "openldap",
SourceDeptParentId: dept.ParentId,
GroupDN: dept.DN,
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncOpenLdapDepts添加其他部门失败:%s", err.Error()))
}
}
return nil, nil
}

// AddGroup 添加部门数据
func (d OpenLdapLogic) AddDepts(group *model.Group) error {
// 判断部门名称是否存在
parentGroup := new(model.Group)
err := isql.Group.Find(tools.H{"source_dept_id": group.SourceDeptParentId}, parentGroup)
if err != nil {
return tools.NewMySqlError(fmt.Errorf("查询父级部门失败:%s", err.Error()))
}
if !isql.Group.Exist(tools.H{"source_dept_id": group.SourceDeptId}) {
group.ParentId = parentGroup.ID
// 在数据库中创建组
err = isql.Group.Add(group)
if err != nil {
return err
}
}
return nil
}

//根据现有数据库同步到的部门信息,开启用户同步
func (d OpenLdapLogic) SyncOpenLdapUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取ldap用户列表
staffs, err := openldap.GetAllUsers()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("获取ldap用户列表失败:%s", err.Error()))
}
// 2.遍历用户,开始写入
for _, staff := range staffs {
groupIds, err := isql.Group.DeptIdsToGroupIds(staff.DepartmentIds)
if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("将部门ids转换为内部部门id失败:%s", err.Error()))
}
// 根据角色id获取角色
roles, err := isql.Role.GetRolesByIds([]uint{2})
if err != nil {
return nil, tools.NewValidatorError(fmt.Errorf("根据角色ID获取角色信息失败:%s", err.Error()))
}
// 入库
err = d.AddUsers(&model.User{
Username: staff.Name,
Nickname: staff.DisplayName,
GivenName: staff.GivenName,
Mail: staff.Mail,
JobNumber: staff.EmployeeNumber,
Mobile: staff.Mobile,
PostalAddress: staff.PostalAddress,
Departments: staff.BusinessCategory,
Position: staff.DepartmentNumber,
Introduction: staff.CN,
Creator: "system",
Source: "openldap",
DepartmentId: tools.SliceToString(groupIds, ","),
SourceUserId: staff.Name,
SourceUnionId: staff.Name,
Roles: roles,
UserDN: staff.DN,
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncOpenLdapUsers写入用户失败:%s", err.Error()))
}
}
return nil, nil
}

// AddUser 添加用户数据
func (d OpenLdapLogic) AddUsers(user *model.User) error {
// 根据 unionid 查询用户,不存在则创建
if !isql.User.Exist(tools.H{"source_union_id": user.SourceUnionId}) {
if user.Departments == "" {
user.Departments = "默认:研发中心"
}
if user.GivenName == "" {
user.GivenName = user.Nickname
}
if user.PostalAddress == "" {
user.PostalAddress = "默认:地球"
}
if user.Position == "" {
user.Position = "默认:技术"
}
if user.Introduction == "" {
user.Introduction = user.Nickname
}
if user.JobNumber == "" {
user.JobNumber = "未启用"
}
// 先将用户添加到MySQL
err := isql.User.Add(user)
if err != nil {
return tools.NewMySqlError(fmt.Errorf("向MySQL创建用户失败:" + err.Error()))
}

// 获取用户将要添加的分组
groups, err := isql.Group.GetGroupByIds(tools.StringToSlice(user.DepartmentId, ","))
if err != nil {
return tools.NewMySqlError(fmt.Errorf("根据部门ID获取部门信息失败" + err.Error()))
}
for _, group := range groups {
if group.GroupDN[:3] == "ou=" {
continue
}
// 先将用户和部门信息维护到MySQL
err := isql.Group.AddUserToGroup(group, []model.User{*user})
if err != nil {
return tools.NewMySqlError(fmt.Errorf("向MySQL添加用户到分组关系失败:" + err.Error()))
}
}
return nil
}
return nil
}
4 changes: 2 additions & 2 deletions model/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (

type Group struct {
gorm.Model
GroupName string `gorm:"type:varchar(20);comment:'分组名称'" json:"groupName"`
Remark string `gorm:"type:varchar(100);comment:'分组中文说明'" json:"remark"`
GroupName string `gorm:"type:varchar(128);comment:'分组名称'" json:"groupName"`
Remark string `gorm:"type:varchar(128);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"`
Expand Down
4 changes: 4 additions & 0 deletions model/request/group_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,7 @@ type SyncWeComDeptsReq struct {
// SyncFeiShuDeptsReq 同步飞书部门信息
type SyncFeiShuDeptsReq struct {
}

// SyncOpenLdapDeptsReq 同步原ldap部门信息
type SyncOpenLdapDeptsReq struct {
}
4 changes: 4 additions & 0 deletions model/request/user_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ type SyncWeComUserReq struct {
type SyncFeiShuUserReq struct {
}

// SyncOpenLdapUserReq 同步ldap用户信息
type SyncOpenLdapUserReq struct {
}

// UserListReq 获取用户列表结构体
type UserListReq struct {
Username string `json:"username" form:"username"`
Expand Down
143 changes: 143 additions & 0 deletions public/client/openldap/openldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package openldap

import (
"fmt"
"strings"

"github.com/eryajf/go-ldap-admin/config"
"github.com/eryajf/go-ldap-admin/public/common"
ldap "github.com/go-ldap/ldap/v3"
)

type Dept struct {
DN string `json:"dn"`
Id string `json:"id"` // 部门ID
Name string `json:"name"` // 部门名称拼音
Remark string `json:"remark"` // 部门中文名
ParentId string `json:"parentid"` // 父部门ID
}

type User struct {
Name string `json:"name"`
DN string `json:"dn"`
CN string `json:"cn"`
SN string `json:"sn"`
Mobile string `json:"mobile"`
BusinessCategory string `json:"businessCategory"` // 业务类别,部门名字
DepartmentNumber string `json:"departmentNumber"` // 部门编号,此处可以存放员工的职位
Description string `json:"description"` // 描述
DisplayName string `json:"displayName"` // 展示名字,可以是中文名字
Mail string `json:"mail"` // 邮箱
EmployeeNumber string `json:"employeeNumber"` // 员工工号
GivenName string `json:"givenName"` // 给定名字,如果公司有花名,可以用这个字段
PostalAddress string `json:"postalAddress"` // 家庭住址
DepartmentIds []string `json:"department_ids"`
}

// GetAllDepts 获取所有部门
func GetAllDepts() (ret []*Dept, err error) {
// Construct query request
searchRequest := ldap.NewSearchRequest(
config.Conf.Ldap.BaseDN, // This is basedn, we will start searching from this node.
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly
"(&(objectClass=*))", // This is Filter for LDAP query
[]string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned
nil,
)
// Search through ldap built-in search
sr, err := common.LDAP.Search(searchRequest)
if err != nil {
return ret, err
}
// Refers to the entry that returns data. If it is greater than 0, the interface returns normally.
if len(sr.Entries) > 0 {
for _, v := range sr.Entries {
if v.DN == config.Conf.Ldap.BaseDN || v.DN == config.Conf.Ldap.AdminDN || strings.Contains(v.DN, config.Conf.Ldap.UserDN) {
continue
}
var ele Dept
ele.DN = v.DN
ele.Name = strings.Split(strings.Split(v.DN, ",")[0], "=")[1]
ele.Id = strings.Split(strings.Split(v.DN, ",")[0], "=")[1]
ele.Remark = v.GetAttributeValue("description")
if len(strings.Split(v.DN, ","))-len(strings.Split(config.Conf.Ldap.BaseDN, ",")) == 1 {
ele.ParentId = "openldap_1"
} else {
ele.ParentId = strings.Split(strings.Split(v.DN, ",")[1], "=")[1]
}
ret = append(ret, &ele)
}
}
return
}

// GetAllUsers 获取所有员工信息
func GetAllUsers() (ret []*User, err error) {
// Construct query request
searchRequest := ldap.NewSearchRequest(
config.Conf.Ldap.BaseDN, // This is basedn, we will start searching from this node.
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly
"(&(objectClass=*))", // This is Filter for LDAP query
[]string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned
nil,
)
// Search through ldap built-in search
sr, err := common.LDAP.Search(searchRequest)
if err != nil {
return ret, err
}
// Refers to the entry that returns data. If it is greater than 0, the interface returns normally.
if len(sr.Entries) > 0 {
for _, v := range sr.Entries {
if v.DN == config.Conf.Ldap.UserDN || !strings.Contains(v.DN, config.Conf.Ldap.UserDN) {
continue
}
name := strings.Split(strings.Split(v.DN, ",")[0], "=")[1]
deptIds, err := GetUserDeptIds(v.DN)
if err != nil {
return ret, err
}
ret = append(ret, &User{
Name: name,
DN: v.DN,
CN: v.GetAttributeValue("cn"),
SN: v.GetAttributeValue("sn"),
Mobile: v.GetAttributeValue("mobile"),
BusinessCategory: v.GetAttributeValue("businessCategory"),
DepartmentNumber: v.GetAttributeValue("departmentNumber"),
Description: v.GetAttributeValue("description"),
DisplayName: v.GetAttributeValue("displayName"),
Mail: v.GetAttributeValue("mail"),
EmployeeNumber: v.GetAttributeValue("employeeNumber"),
GivenName: v.GetAttributeValue("givenName"),
PostalAddress: v.GetAttributeValue("postalAddress"),
DepartmentIds: deptIds,
})
}
}
return
}

// GetUserDeptIds 获取用户所在的部门
func GetUserDeptIds(udn string) (ret []string, err error) {
// Construct query request
searchRequest := ldap.NewSearchRequest(
config.Conf.Ldap.BaseDN, // This is basedn, we will start searching from this node.
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly
fmt.Sprintf("(|(Member=%s)(uniqueMember=%s))", udn, udn), // This is Filter for LDAP query
[]string{}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned
nil,
)
// Search through ldap built-in search
sr, err := common.LDAP.Search(searchRequest)
if err != nil {
return ret, err
}
// Refers to the entry that returns data. If it is greater than 0, the interface returns normally.
if len(sr.Entries) > 0 {
for _, v := range sr.Entries {
ret = append(ret, strings.Split(strings.Split(v.DN, ",")[0], "=")[1])
}
}
return ret, nil
}
Loading