Files
KubePi/internal/service/v1/ldap/ldap.go
2022-06-15 16:10:28 +08:00

426 lines
11 KiB
Go

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
}