diff --git a/pkg/models/auth/password.go b/pkg/models/auth/password.go index cc14d5e76..aab74f85a 100644 --- a/pkg/models/auth/password.go +++ b/pkg/models/auth/password.go @@ -76,7 +76,8 @@ func (p *passwordAuthenticator) Authenticate(_ context.Context, username, passwo return nil, providerOptions.Name, err } linkedAccount, err := p.userGetter.findMappedUser(providerOptions.Name, authenticated.GetUserID()) - if err != nil { + if err != nil && !errors.IsNotFound(err) { + klog.Error(err) return nil, providerOptions.Name, err } // using this method requires you to manually provision users. diff --git a/pkg/models/auth/password_test.go b/pkg/models/auth/password_test.go index 9853f79b6..bb0af5667 100644 --- a/pkg/models/auth/password_test.go +++ b/pkg/models/auth/password_test.go @@ -19,9 +19,22 @@ package auth import ( + "context" + "reflect" "testing" + "github.com/mitchellh/mapstructure" "golang.org/x/crypto/bcrypt" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apiserver/pkg/authentication/user" + authuser "k8s.io/apiserver/pkg/authentication/user" + iamv1alpha2 "kubesphere.io/api/iam/v1alpha2" + + "kubesphere.io/kubesphere/pkg/apiserver/authentication" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake" + ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions" ) func TestEncryptPassword(t *testing.T) { @@ -39,3 +52,197 @@ func hashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) return string(bytes), err } + +func Test_passwordAuthenticator_Authenticate(t *testing.T) { + + oauthOptions := &authentication.Options{ + OAuthOptions: &oauth.Options{ + IdentityProviders: []oauth.IdentityProviderOptions{ + { + Name: "fakepwd", + MappingMethod: "auto", + Type: "fakePasswordProvider", + Provider: oauth.DynamicOptions{ + "identities": map[string]interface{}{ + "user1": map[string]string{ + "uid": "100001", + "email": "user1@kubesphere.io", + "username": "user1", + "password": "password", + }, + "user2": map[string]string{ + "uid": "100002", + "email": "user2@kubesphere.io", + "username": "user2", + "password": "password", + }, + }, + }, + }, + }, + }, + } + + identityprovider.RegisterGenericProvider(&fakePasswordProviderFactory{}) + if err := identityprovider.SetupWithOptions(oauthOptions.OAuthOptions.IdentityProviders); err != nil { + t.Fatal(err) + } + + ksClient := fakeks.NewSimpleClientset() + ksInformerFactory := ksinformers.NewSharedInformerFactory(ksClient, 0) + err := ksInformerFactory.Iam().V1alpha2().Users().Informer().GetIndexer().Add(newUser("user1", "100001", "fakepwd")) + err = ksInformerFactory.Iam().V1alpha2().Users().Informer().GetIndexer().Add(newUser("user3", "100003", "")) + err = ksInformerFactory.Iam().V1alpha2().Users().Informer().GetIndexer().Add(newActiveUser("user4", "password")) + + if err != nil { + t.Fatal(err) + } + + authenticator := NewPasswordAuthenticator( + ksClient, + ksInformerFactory.Iam().V1alpha2().Users().Lister(), + oauthOptions, + ) + + type args struct { + ctx context.Context + username string + password string + } + tests := []struct { + name string + passwordAuthenticator PasswordAuthenticator + args args + want authuser.Info + want1 string + wantErr bool + }{ + { + name: "Should successfully with existing provider user", + passwordAuthenticator: authenticator, + args: args{ + ctx: context.Background(), + username: "user1", + password: "password", + }, + want: &user.DefaultInfo{ + Name: "user1", + }, + wantErr: false, + }, + { + name: "Should return register user", + passwordAuthenticator: authenticator, + args: args{ + ctx: context.Background(), + username: "user2", + password: "password", + }, + want: &user.DefaultInfo{ + Name: "system:pre-registration", + Extra: map[string][]string{ + "email": {"user2@kubesphere.io"}, + "idp": {"fakepwd"}, + "uid": {"100002"}, + "username": {"user2"}, + }, + }, + wantErr: false, + }, + { + name: "Should failed login", + passwordAuthenticator: authenticator, + args: args{ + ctx: context.Background(), + username: "user3", + password: "password", + }, + wantErr: true, + }, + { + name: "Should successfully with internal user", + passwordAuthenticator: authenticator, + args: args{ + ctx: context.Background(), + username: "user4", + password: "password", + }, + want: &user.DefaultInfo{ + Name: "user4", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := tt.passwordAuthenticator + got, _, err := p.Authenticate(tt.args.ctx, tt.args.username, tt.args.password) + if (err != nil) != tt.wantErr { + t.Errorf("passwordAuthenticator.Authenticate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("passwordAuthenticator.Authenticate() got = %v, want %v", got, tt.want) + } + }) + } +} + +type fakePasswordProviderFactory struct { +} + +type fakePasswordProvider struct { + Identities map[string]fakePasswordIdentity `json:"identities"` +} + +type fakePasswordIdentity struct { + UID string `json:"uid"` + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} + +func (f fakePasswordIdentity) GetUserID() string { + return f.UID +} + +func (f fakePasswordIdentity) GetUsername() string { + return f.Username +} + +func (f fakePasswordIdentity) GetEmail() string { + return f.Email +} + +func (fakePasswordProviderFactory) Type() string { + return "fakePasswordProvider" +} + +func (fakePasswordProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.GenericProvider, error) { + var fakeProvider fakePasswordProvider + if err := mapstructure.Decode(options, &fakeProvider); err != nil { + return nil, err + } + return &fakeProvider, nil +} + +func (l fakePasswordProvider) Authenticate(username string, password string) (identityprovider.Identity, error) { + if i, ok := l.Identities[username]; ok && i.Password == password { + return i, nil + } + return nil, errors.NewUnauthorized("authorization failed") +} + +func encrypt(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes), err +} + +func newActiveUser(username string, password string) *iamv1alpha2.User { + u := newUser(username, "", "") + password, _ = encrypt(password) + u.Spec.EncryptedPassword = password + s := iamv1alpha2.UserActive + u.Status.State = &s + return u +}