package ldap import ( "encoding/json" "errors" "fmt" v1 "github.com/KubeOperator/kubepi/internal/model/v1" v1Ldap "github.com/KubeOperator/kubepi/internal/model/v1/ldap" v1Role "github.com/KubeOperator/kubepi/internal/model/v1/role" v1User "github.com/KubeOperator/kubepi/internal/model/v1/user" "github.com/KubeOperator/kubepi/internal/server" "github.com/KubeOperator/kubepi/internal/service/v1/common" "github.com/KubeOperator/kubepi/internal/service/v1/rolebinding" "github.com/KubeOperator/kubepi/internal/service/v1/user" ldapClient "github.com/KubeOperator/kubepi/pkg/util/ldap" "github.com/asdine/storm/v3" "github.com/asdine/storm/v3/q" "github.com/google/uuid" "reflect" "strings" "time" ) type Service interface { common.DBService Create(ldap *v1Ldap.Ldap, options common.DBOptions) error List(options common.DBOptions) ([]v1Ldap.Ldap, error) Update(id string, ldap *v1Ldap.Ldap, options common.DBOptions) error GetById(id string, options common.DBOptions) (*v1Ldap.Ldap, error) Delete(id string, options common.DBOptions) error Sync(id string, options common.DBOptions) error Login(user v1User.User, password string, options common.DBOptions) error TestConnect(ldap *v1Ldap.Ldap) (int, error) TestLogin(username string, password string) error ImportUsers(users []v1User.ImportUser) (v1User.ImportResult, error) CheckStatus() bool GetLdapUser() ([]v1User.ImportUser, error) } func NewService() Service { return &service{ userService: user.NewService(), roleBindingService: rolebinding.NewService(), } } type service struct { common.DefaultDBService userService user.Service roleBindingService rolebinding.Service } func (l *service) Create(ldap *v1Ldap.Ldap, options common.DBOptions) error { m := make(map[string]string) err := json.Unmarshal([]byte(ldap.Mapping), &m) if err != nil { return err } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) err = lc.Connect() if err != nil { return err } db := l.GetDB(options) ldap.UUID = uuid.New().String() ldap.CreateAt = time.Now() ldap.UpdateAt = time.Now() return db.Save(ldap) } func (l *service) List(options common.DBOptions) ([]v1Ldap.Ldap, error) { db := l.GetDB(options) ldap := make([]v1Ldap.Ldap, 0) if err := db.All(&ldap); err != nil { return nil, err } return ldap, nil } func (l *service) Update(id string, ldap *v1Ldap.Ldap, options common.DBOptions) error { m := make(map[string]string) err := json.Unmarshal([]byte(ldap.Mapping), &m) if err != nil { return err } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return err } old, err := l.GetById(id, options) if err != nil { return err } ldap.UUID = old.UUID ldap.CreateAt = old.CreateAt ldap.UpdateAt = time.Now() db := l.GetDB(options) if ldap.Enable != old.Enable { err = db.UpdateField(ldap, "Enable", ldap.Enable) if err != nil { return err } } if ldap.TLS != old.TLS { err = db.UpdateField(ldap, "TLS", ldap.TLS) if err != nil { return err } } return db.Update(ldap) } func (l *service) GetById(id string, options common.DBOptions) (*v1Ldap.Ldap, error) { db := l.GetDB(options) var ldap v1Ldap.Ldap query := db.Select(q.Eq("UUID", id)) if err := query.First(&ldap); err != nil { return nil, err } return &ldap, nil } func (l *service) Delete(id string, options common.DBOptions) error { db := l.GetDB(options) ldap, err := l.GetById(id, options) if err != nil { return err } return db.DeleteStruct(ldap) } func (l *service) GetLdapUser() ([]v1User.ImportUser, error) { users := []v1User.ImportUser{} ldaps, err := l.List(common.DBOptions{}) if err != nil { return users, err } if len(ldaps) == 0 { return users, errors.New("请先保存LDAP配置") } ldap := ldaps[0] if !ldap.Enable { return users, errors.New("请先启用LDAP") } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return users, err } attributes, err := ldap.GetAttributes() if err != nil { return users, err } mappings, err := ldap.GetMappings() if err != nil { return users, err } entries, err := lc.Search(ldap.Dn, ldap.Filter, ldap.SizeLimit, ldap.TimeLimit, attributes) if err != nil { return users, err } if len(entries) == 0 { return users, nil } for _, entry := range entries { us := new(v1User.ImportUser) us.Available = true rv := reflect.ValueOf(&us).Elem().Elem() for _, at := range entry.Attributes { for k, v := range mappings { if v == at.Name && len(at.Values) > 0 { fv := rv.FieldByName(k) if fv.IsValid() { fv.Set(reflect.ValueOf(strings.Trim(at.Values[0], " "))) } } } } if us.Name == "" { continue } _, err = l.userService.GetByNameOrEmail(us.Name, common.DBOptions{}) if err == nil { us.Available = false } users = append(users, *us) } return users, nil } func (l *service) TestConnect(ldap *v1Ldap.Ldap) (int, error) { users := 0 if !ldap.Enable { return users, errors.New("请先启用LDAP") } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return users, err } attributes, err := ldap.GetAttributes() if err != nil { return users, err } entries, err := lc.Search(ldap.Dn, ldap.Filter, ldap.SizeLimit, ldap.TimeLimit, attributes) if err != nil { return users, err } if len(entries) == 0 { return users, nil } return len(entries), nil } func (l *service) CheckStatus() bool { ldaps, err := l.List(common.DBOptions{}) if err != nil || len(ldaps) == 0 { return false } ldap := ldaps[0] return ldap.Enable } func (l *service) TestLogin(username string, password string) error { ldaps, err := l.List(common.DBOptions{}) if err != nil { return err } if len(ldaps) == 0 { return errors.New("请先保存LDAP配置") } ldap := ldaps[0] mappings, err := ldap.GetMappings() if err != nil { return err } var userFilter string for k, v := range mappings { if k == "Name" { userFilter = "(" + v + "=" + username + ")" } } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return err } return lc.Login(ldap.Dn, userFilter, password, ldap.SizeLimit, ldap.TimeLimit) } func (l *service) Login(user v1User.User, password string, options common.DBOptions) error { ldaps, err := l.List(options) if err != nil { return err } ldap := ldaps[0] mappings, err := ldap.GetMappings() if err != nil { return err } var userFilter string for k, v := range mappings { if k == "Name" { userFilter = "(" + v + "=" + user.Name + ")" } } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return err } return lc.Login(ldap.Dn, userFilter, password, ldap.SizeLimit, ldap.TimeLimit) } func (l *service) ImportUsers(users []v1User.ImportUser) (v1User.ImportResult, error) { var result v1User.ImportResult for _, imp := range users { us := &v1User.User{ NickName: imp.NickName, Metadata: v1.Metadata{ Name: imp.Name, }, Type: v1User.LDAP, Email: imp.Email, } if us.Email == "" { us.Email = us.Name + "@example.com" } if us.NickName == "" { us.NickName = us.Name } result.Failures = append(result.Failures, us.Name) tx, err := server.DB().Begin(true) if err != nil { server.Logger().Errorf("create tx err: %s", err) continue } err = l.userService.Create(us, common.DBOptions{DB: tx}) if err != nil { _ = tx.Rollback() server.Logger().Errorf("can not insert user %s , err: %s", us.Name, err) continue } roleName := "Common User" binding := v1Role.Binding{ BaseModel: v1.BaseModel{ Kind: "RoleBind", ApiVersion: "v1", CreatedBy: "admin", }, Metadata: v1.Metadata{ Name: fmt.Sprintf("role-binding-%s-%s", roleName, us.Name), }, Subject: v1Role.Subject{ Kind: "User", Name: us.Name, }, RoleRef: roleName, } if err := l.roleBindingService.CreateRoleBinding(&binding, common.DBOptions{DB: tx}); err != nil { _ = tx.Rollback() server.Logger().Errorf("can not create user role %s , err: %s", us.Name, err) continue } _ = tx.Commit() result.Failures = result.Failures[:len(result.Failures)-1] } if len(result.Failures) == 0 { result.Success = true } return result, nil } func (l *service) Sync(id string, options common.DBOptions) error { ldap, err := l.GetById(id, options) if err != nil { return err } lc := ldapClient.NewLdapClient(ldap.Address, ldap.Port, ldap.Username, ldap.Password, ldap.TLS) if err := lc.Connect(); err != nil { return err } go func() { server.Logger().Info("start sync ldap user") insertCount := 0 attributes, err := ldap.GetAttributes() if err != nil { server.Logger().Errorf("can not get ldap map attributes") return } mappings, err := ldap.GetMappings() if err != nil { server.Logger().Errorf("can not get ldap mappings") return } entries, err := lc.Search(ldap.Dn, ldap.Filter, ldap.SizeLimit, ldap.TimeLimit, attributes) if err != nil { server.Logger().Errorf(err.Error()) return } for _, entry := range entries { us := new(v1User.User) rv := reflect.ValueOf(&us).Elem().Elem() for _, at := range entry.Attributes { for k, v := range mappings { if v == at.Name && len(at.Values) > 0 { fv := rv.FieldByName(k) if fv.IsValid() { fv.Set(reflect.ValueOf(strings.Trim(at.Values[0], " "))) } } } } if us.Email == "" || us.Name == "" { continue } if us.NickName == "" { us.NickName = us.Name } us.Type = v1User.LDAP _, err := l.userService.GetByNameOrEmail(us.Name, options) if errors.Is(err, storm.ErrNotFound) { tx, err := server.DB().Begin(true) if err != nil { server.Logger().Errorf("create tx err: %s", err) } err = l.userService.Create(us, common.DBOptions{DB: tx}) if err != nil { _ = tx.Rollback() server.Logger().Errorf("can not insert user %s , err: %s", us.Name, err) continue } roleName := "Common User" binding := v1Role.Binding{ BaseModel: v1.BaseModel{ Kind: "RoleBind", ApiVersion: "v1", CreatedBy: "admin", }, Metadata: v1.Metadata{ Name: fmt.Sprintf("role-binding-%s-%s", roleName, us.Name), }, Subject: v1Role.Subject{ Kind: "User", Name: us.Name, }, RoleRef: roleName, } if err := l.roleBindingService.CreateRoleBinding(&binding, common.DBOptions{DB: tx}); err != nil { _ = tx.Rollback() server.Logger().Errorf("can not create user role %s , err: %s", us.Name, err) continue } _ = tx.Commit() insertCount++ } } server.Logger().Infof("sync ldap user %d , insert user %d", len(entries), insertCount) }() return nil }