Merge pull request #2270 from wansir/ldap

fix: synchronize users to LDAP
This commit is contained in:
KubeSphere CI Bot
2020-07-02 17:54:44 +08:00
committed by GitHub
9 changed files with 126 additions and 106 deletions

View File

@@ -48,6 +48,7 @@ import (
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -60,6 +61,7 @@ func addControllers(
informerFactory informers.InformerFactory,
devopsClient devops.Interface,
s3Client s3.Interface,
ldapClient ldapclient.Interface,
openpitrixClient openpitrix.Client,
multiClusterEnabled bool,
networkPolicyEnabled bool,
@@ -207,8 +209,8 @@ func addControllers(
}
userController := user.NewController(client.Kubernetes(), client.KubeSphere(), client.Config(),
kubesphereInformer.Iam().V1alpha2().Users(),
fedUserCache, fedUserCacheController, kubernetesInformer.Core().V1().ConfigMaps(), multiClusterEnabled)
kubesphereInformer.Iam().V1alpha2().Users(), fedUserCache, fedUserCacheController,
kubernetesInformer.Core().V1().ConfigMaps(), ldapClient, multiClusterEnabled)
csrController := certificatesigningrequest.NewController(client.Kubernetes(), kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(),
kubernetesInformer.Core().V1().ConfigMaps(), client.Config())

View File

@@ -8,6 +8,7 @@ import (
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/multicluster"
"kubesphere.io/kubesphere/pkg/simple/client/network"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
@@ -21,6 +22,7 @@ type KubeSphereControllerManagerOptions struct {
KubernetesOptions *k8s.KubernetesOptions
DevopsOptions *jenkins.Options
S3Options *s3.Options
LdapOptions *ldapclient.Options
OpenPitrixOptions *openpitrix.Options
NetworkOptions *network.Options
MultiClusterOptions *multicluster.Options
@@ -35,6 +37,7 @@ func NewKubeSphereControllerManagerOptions() *KubeSphereControllerManagerOptions
KubernetesOptions: k8s.NewKubernetesOptions(),
DevopsOptions: jenkins.NewDevopsOptions(),
S3Options: s3.NewS3Options(),
LdapOptions: ldapclient.NewOptions(),
OpenPitrixOptions: openpitrix.NewOptions(),
NetworkOptions: network.NewNetworkOptions(),
MultiClusterOptions: multicluster.NewOptions(),
@@ -57,6 +60,7 @@ func (s *KubeSphereControllerManagerOptions) Flags() cliflag.NamedFlagSets {
s.KubernetesOptions.AddFlags(fss.FlagSet("kubernetes"), s.KubernetesOptions)
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
s.LdapOptions.AddFlags(fss.FlagSet("ldap"), s.LdapOptions)
s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
s.NetworkOptions.AddFlags(fss.FlagSet("network"), s.NetworkOptions)
s.MultiClusterOptions.AddFlags(fss.FlagSet("multicluster"), s.MultiClusterOptions)
@@ -92,6 +96,7 @@ func (s *KubeSphereControllerManagerOptions) Validate() []error {
errs = append(errs, s.S3Options.Validate()...)
errs = append(errs, s.OpenPitrixOptions.Validate()...)
errs = append(errs, s.NetworkOptions.Validate()...)
errs = append(errs, s.LdapOptions.Validate()...)
return errs
}

View File

@@ -40,6 +40,7 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/utils/term"
@@ -58,6 +59,7 @@ func NewControllerManagerCommand() *cobra.Command {
KubernetesOptions: conf.KubernetesOptions,
DevopsOptions: conf.DevopsOptions,
S3Options: conf.S3Options,
LdapOptions: conf.LdapOptions,
OpenPitrixOptions: conf.OpenPitrixOptions,
NetworkOptions: conf.NetworkOptions,
MultiClusterOptions: conf.MultiClusterOptions,
@@ -114,6 +116,22 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
}
}
var ldapClient ldapclient.Interface
if s.LdapOptions == nil || len(s.LdapOptions.Host) == 0 {
klog.Errorf("Failed to create devops client invalid ldap options")
return err
}
if s.LdapOptions.Host == ldapclient.FAKE_HOST {
ldapClient = ldapclient.NewSimpleLdap()
} else {
ldapClient, err = ldapclient.NewLdapClient(s.LdapOptions, stopCh)
if err != nil {
klog.Errorf("Failed to create ldap client %v", err)
return err
}
}
var openpitrixClient openpitrix.Client
if s.OpenPitrixOptions != nil && !s.OpenPitrixOptions.IsEmpty() {
openpitrixClient, err = openpitrix.NewClient(s.OpenPitrixOptions)
@@ -160,7 +178,8 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{})
// TODO(jeff): refactor config with CRD
servicemeshEnabled := s.ServiceMeshOptions != nil && len(s.ServiceMeshOptions.IstioPilotHost) != 0
if err = addControllers(mgr, kubernetesClient, informerFactory, devopsClient, s3Client, openpitrixClient, s.MultiClusterOptions.Enable, s.NetworkOptions.EnableNetworkPolicy, servicemeshEnabled, stopCh); err != nil {
if err = addControllers(mgr, kubernetesClient, informerFactory, devopsClient, s3Client, ldapClient, openpitrixClient,
s.MultiClusterOptions.Enable, s.NetworkOptions.EnableNetworkPolicy, servicemeshEnabled, stopCh); err != nil {
klog.Fatalf("unable to register controllers to the manager: %v", err)
}

View File

@@ -15,7 +15,6 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
eventsclient "kubesphere.io/kubesphere/pkg/simple/client/events/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
esclient "kubesphere.io/kubesphere/pkg/simple/client/logging/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring/prometheus"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
@@ -53,7 +52,6 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
s.AuthorizationOptions.AddFlags(fss.FlagSet("authorization"), s.AuthorizationOptions)
s.DevopsOptions.AddFlags(fss.FlagSet("devops"), s.DevopsOptions)
s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions)
s.LdapOptions.AddFlags(fss.FlagSet("ldap"), s.LdapOptions)
s.RedisOptions.AddFlags(fss.FlagSet("redis"), s.RedisOptions)
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
s.OpenPitrixOptions.AddFlags(fss.FlagSet("openpitrix"), s.OpenPitrixOptions)
@@ -138,18 +136,6 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS
apiServer.SonarClient = sonarqube.NewSonar(sonarClient.SonarQube())
}
if s.LdapOptions.Host != "" {
if s.LdapOptions.Host == fakeInterface && s.DebugMode {
apiServer.LdapClient = ldap.NewSimpleLdap()
} else {
ldapClient, err := ldap.NewLdapClient(s.LdapOptions, stopCh)
if err != nil {
return nil, err
}
apiServer.LdapClient = ldapClient
}
}
var cacheClient cache.Interface
if s.RedisOptions.Host != "" {
if s.RedisOptions.Host == fakeInterface && s.DebugMode {

View File

@@ -69,7 +69,6 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/events"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/simple/client/logging"
"kubesphere.io/kubesphere/pkg/simple/client/monitoring"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
@@ -131,9 +130,6 @@ type APIServer struct {
//
S3Client s3.Interface
//
LdapClient ldap.Interface
SonarClient sonarqube.SonarInterface
EventsClient events.Client

View File

@@ -43,6 +43,8 @@ import (
userlister "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/kubeconfig"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"strconv"
@@ -67,6 +69,7 @@ type Controller struct {
cmSynced cache.InformerSynced
fedUserCache cache.Store
fedUserController cache.Controller
ldapClient ldapclient.Interface
// workqueue is a rate limited work queue. This is used to queue work to be
// processed instead of performing it as soon as a change happens. This
// means we can ensure we only process a fixed amount of resources at a
@@ -81,7 +84,7 @@ type Controller struct {
func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface,
config *rest.Config, userInformer userinformer.UserInformer, fedUserCache cache.Store, fedUserController cache.Controller,
configMapInformer corev1informers.ConfigMapInformer, multiClusterEnabled bool) *Controller {
configMapInformer corev1informers.ConfigMapInformer, ldapClient ldapclient.Interface, multiClusterEnabled bool) *Controller {
// Create event broadcaster
// Add sample-controller types to the default Kubernetes Scheme so Events can be
// logged for sample-controller types.
@@ -106,6 +109,7 @@ func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface
cmSynced: configMapInformer.Informer().HasSynced,
fedUserCache: fedUserCache,
fedUserController: fedUserController,
ldapClient: ldapClient,
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Users"),
recorder: recorder,
multiClusterEnabled: multiClusterEnabled,
@@ -239,6 +243,48 @@ func (c *Controller) reconcile(key string) error {
return err
}
// name of your custom finalizer
finalizer := "finalizers.kubesphere.io/users"
if user.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object.
if !sliceutil.HasString(user.Finalizers, finalizer) {
user.ObjectMeta.Finalizers = append(user.ObjectMeta.Finalizers, finalizer)
if user, err = c.ksClient.IamV1alpha2().Users().Update(user); err != nil {
return err
}
}
} else {
// The object is being deleted
if sliceutil.HasString(user.ObjectMeta.Finalizers, finalizer) {
klog.V(4).Infof("delete user %s", key)
if err = c.ldapClient.Delete(key); err != nil && err != ldapclient.ErrUserNotExists {
klog.Error(err)
return err
}
// remove our finalizer from the list and update it.
user.Finalizers = sliceutil.RemoveString(user.ObjectMeta.Finalizers, func(item string) bool {
return item == finalizer
})
if _, err := c.ksClient.IamV1alpha2().Users().Update(user); err != nil {
return err
}
}
// Our finalizer has finished, so the reconciler can do nothing.
return nil
}
if err = c.ldapSync(user); err != nil {
klog.Error(err)
return err
}
if user, err = c.ensurePasswordIsEncrypted(user); err != nil {
klog.Error(err)
return err
@@ -269,9 +315,9 @@ func (c *Controller) Start(stopCh <-chan struct{}) error {
}
func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
encrypted, err := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
encrypted, _ := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
// password is not encrypted
if err != nil || !encrypted {
if !encrypted {
password, err := encrypt(user.Spec.EncryptedPassword)
if err != nil {
klog.Error(err)
@@ -282,7 +328,6 @@ func (c *Controller) ensurePasswordIsEncrypted(user *iamv1alpha2.User) (*iamv1al
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = "true"
user.Status.State = iamv1alpha2.UserActive
return c.ksClient.IamV1alpha2().Users().Update(user)
@@ -419,6 +464,25 @@ func (c *Controller) updateFederatedUser(fedUser *iamv1alpha2.FederatedUser) err
return nil
}
func (c *Controller) ldapSync(user *iamv1alpha2.User) error {
encrypted, _ := strconv.ParseBool(user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation])
if encrypted {
return nil
}
_, err := c.ldapClient.Get(user.Name)
if err != nil {
if err == ldapclient.ErrUserNotExists {
klog.V(4).Infof("create user %s", user.Name)
return c.ldapClient.Create(user)
}
klog.Error(err)
return err
} else {
klog.V(4).Infof("update user %s", user.Name)
return c.ldapClient.Update(user)
}
}
func encrypt(password string) (string, error) {
// when user is already mapped to another identity, password is empty by default
// unable to log in directly until password reset

View File

@@ -29,6 +29,7 @@ import (
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"reflect"
"testing"
"time"
@@ -81,6 +82,7 @@ func newUser(name string) *iamv1alpha2.User {
func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactory, kubeinformers.SharedInformerFactory) {
f.ksclient = fake.NewSimpleClientset(f.objects...)
f.k8sclient = k8sfake.NewSimpleClientset(f.kubeobjects...)
ldapClient := ldapclient.NewSimpleLdap()
ksinformers := ksinformers.NewSharedInformerFactory(f.ksclient, noResyncPeriodFunc())
k8sinformers := kubeinformers.NewSharedInformerFactory(f.k8sclient, noResyncPeriodFunc())
@@ -92,7 +94,7 @@ func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactor
}
}
c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users(), nil, nil, k8sinformers.Core().V1().ConfigMaps(), false)
c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users(), nil, nil, k8sinformers.Core().V1().ConfigMaps(), ldapClient, false)
c.userSynced = alwaysReady
c.recorder = &record.FakeRecorder{}
@@ -208,6 +210,7 @@ func filterInformerActions(actions []core.Action) []core.Action {
var ret []core.Action
for _, action := range actions {
if action.Matches("list", "users") ||
action.Matches("list", "configmaps") ||
action.Matches("watch", "users") {
continue
}
@@ -219,9 +222,14 @@ func filterInformerActions(actions []core.Action) []core.Action {
func (f *fixture) expectUpdateUserStatusAction(user *iamv1alpha2.User) {
expect := user.DeepCopy()
expect.Finalizers = []string{"finalizers.kubesphere.io/users"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action)
expect = expect.DeepCopy()
expect.Status.State = iamv1alpha2.UserActive
expect.Annotations = map[string]string{iamv1alpha2.PasswordEncryptedAnnotation: "true"}
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
action = core.NewUpdateAction(schema.GroupVersionResource{Resource: "users"}, "", expect)
f.actions = append(f.actions, action)
}

View File

@@ -19,7 +19,6 @@ package ldap
import (
"fmt"
"github.com/go-ldap/ldap"
"github.com/google/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
@@ -184,7 +183,7 @@ func (l *ldapInterfaceImpl) dnForUsername(username string) string {
}
func (l *ldapInterfaceImpl) filterForUsername(username string) string {
return fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=%s)(mail=%s)))", username, username)
return fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(%s=%s)(%s=%s)))", ldapAttributeUserID, username, ldapAttributeMail, username)
}
func (l *ldapInterfaceImpl) Get(name string) (*iamv1alpha2.User, error) {
@@ -205,7 +204,6 @@ func (l *ldapInterfaceImpl) Get(name string) (*iamv1alpha2.User, error) {
Attributes: []string{
ldapAttributeMail,
ldapAttributeDescription,
ldapAttributePreferredLanguage,
ldapAttributeCreateTimestamp,
},
}
@@ -215,7 +213,7 @@ func (l *ldapInterfaceImpl) Get(name string) (*iamv1alpha2.User, error) {
return nil, err
}
if len(searchResults.Entries) != 1 {
if len(searchResults.Entries) == 0 {
return nil, ErrUserNotExists
}
@@ -227,28 +225,22 @@ func (l *ldapInterfaceImpl) Get(name string) (*iamv1alpha2.User, error) {
},
Spec: iamv1alpha2.UserSpec{
Email: userEntry.GetAttributeValue(ldapAttributeMail),
Lang: userEntry.GetAttributeValue(ldapAttributePreferredLanguage),
Description: userEntry.GetAttributeValue(ldapAttributeDescription),
},
}
createTimestamp, _ := time.Parse(ldapAttributeCreateTimestampLayout, userEntry.GetAttributeValue(ldapAttributeCreateTimestamp))
user.ObjectMeta.CreationTimestamp.Time = createTimestamp
return user, nil
}
func (l *ldapInterfaceImpl) Create(user *iamv1alpha2.User) error {
if _, err := l.Get(user.Name); err != nil {
return ErrUserAlreadyExisted
}
createRequest := &ldap.AddRequest{
DN: l.dnForUsername(user.Name),
Attributes: []ldap.Attribute{
{
Type: ldapAttributeObjectClass,
Vals: []string{"inetOrgPerson", "posixAccount", "top"},
Vals: []string{"inetOrgPerson", "top"},
},
{
Type: ldapAttributeCommonName,
@@ -256,40 +248,12 @@ func (l *ldapInterfaceImpl) Create(user *iamv1alpha2.User) error {
},
{
Type: ldapAttributeSerialNumber,
Vals: []string{" "},
},
{
Type: ldapAttributeGlobalIDNumber,
Vals: []string{"500"},
},
{
Type: ldapAttributeHomeDirectory,
Vals: []string{"/home/" + user.Name},
},
{
Type: ldapAttributeUserID,
Vals: []string{user.Name},
},
{
Type: ldapAttributeUserIDNumber,
Vals: []string{uuid.New().String()},
},
{
Type: ldapAttributeMail,
Vals: []string{user.Spec.Email},
},
{
Type: ldapAttributeUserPassword,
Vals: []string{user.Spec.EncryptedPassword},
},
{
Type: ldapAttributePreferredLanguage,
Vals: []string{user.Spec.Lang},
},
{
Type: ldapAttributeDescription,
Vals: []string{user.Spec.Description},
},
},
}
@@ -341,14 +305,6 @@ func (l *ldapInterfaceImpl) Update(newUser *iamv1alpha2.User) error {
DN: l.dnForUsername(newUser.Name),
}
if newUser.Spec.Description != "" {
modifyRequest.Replace(ldapAttributeDescription, []string{newUser.Spec.Description})
}
if newUser.Spec.Lang != "" {
modifyRequest.Replace(ldapAttributePreferredLanguage, []string{newUser.Spec.Lang})
}
if newUser.Spec.EncryptedPassword != "" {
modifyRequest.Replace(ldapAttributeUserPassword, []string{newUser.Spec.EncryptedPassword})
}
@@ -385,55 +341,37 @@ func (l *ldapInterfaceImpl) List(query *query.Query) (*api.ListResult, error) {
filter := "(&(objectClass=inetOrgPerson))"
if keyword := query.Filters["keyword"]; keyword != "" {
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword)
}
if username := query.Filters["username"]; username != "" {
uidFilter := ""
for _, username := range strings.Split(string(username), "|") {
uidFilter += fmt.Sprintf("(uid=%s)", username)
}
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", uidFilter)
}
if email := query.Filters["email"]; email != "" {
emailFilter := ""
for _, username := range strings.Split(string(email), "|") {
emailFilter += fmt.Sprintf("(mail=%s)", username)
}
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", emailFilter)
}
for {
userSearchRequest := ldap.NewSearchRequest(
l.userSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"uid", "mail", "description", "preferredLanguage", "createTimestamp"},
[]string{ldapAttributeUserID, ldapAttributeMail, ldapAttributeDescription, ldapAttributeCreateTimestamp},
[]ldap.Control{pageControl},
)
response, err := conn.Search(userSearchRequest)
if err != nil {
klog.Errorln("search user", err)
klog.Error(err)
return nil, err
}
for _, entry := range response.Entries {
uid := entry.GetAttributeValue("uid")
email := entry.GetAttributeValue("mail")
description := entry.GetAttributeValue("description")
lang := entry.GetAttributeValue("preferredLanguage")
createTimestamp, _ := time.Parse("20060102150405Z", entry.GetAttributeValue("createTimestamp"))
uid := entry.GetAttributeValue(ldapAttributeUserID)
email := entry.GetAttributeValue(ldapAttributeMail)
description := entry.GetAttributeValue(ldapAttributeDescription)
createTimestamp, _ := time.Parse(ldapAttributeCreateTimestampLayout, entry.GetAttributeValue(ldapAttributeCreateTimestamp))
user := iamv1alpha2.User{ObjectMeta: metav1.ObjectMeta{Name: uid, CreationTimestamp: metav1.Time{Time: createTimestamp}}, Spec: iamv1alpha2.UserSpec{
Email: email,
Lang: lang,
Description: description,
}}
user := iamv1alpha2.User{
ObjectMeta: metav1.ObjectMeta{
Name: uid,
CreationTimestamp: metav1.Time{Time: createTimestamp}},
Spec: iamv1alpha2.UserSpec{
Email: email,
Description: description,
}}
users = append(users, user)
}

View File

@@ -23,6 +23,8 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/query"
)
const FAKE_HOST string = "FAKE"
// simpleLdap is a implementation of ldap.Interface, you should never use this in production env!
type simpleLdap struct {
store map[string]*iamv1alpha2.User