feat(auth): support multiple identity provider associations (#6299)
Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ package identityprovider
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -35,6 +36,8 @@ const (
|
||||
|
||||
var ErrorIdentityProviderNotFound = errors.New("the Identity provider was not found")
|
||||
|
||||
var IdentityProviderIsDisabled = errors.New("the Identity provider was Disabled")
|
||||
|
||||
type MappingMethod string
|
||||
|
||||
type Configuration struct {
|
||||
@@ -52,6 +55,15 @@ type Configuration struct {
|
||||
// The type of identity provider
|
||||
Type string `json:"type" yaml:"type"`
|
||||
|
||||
// The hidden of login button
|
||||
Hidden bool `json:"hidden" yaml:"hidden"`
|
||||
|
||||
// The disabled of identify provider
|
||||
Disabled bool `json:"disabled" yaml:"disabled"`
|
||||
|
||||
// The display name of identify provider
|
||||
DisplayName string `json:"displayName" yaml:"displayName"`
|
||||
|
||||
// The options of identify provider
|
||||
ProviderOptions options.DynamicOptions `json:"provider" yaml:"provider"`
|
||||
}
|
||||
@@ -72,19 +84,19 @@ type configurationGetter struct {
|
||||
func (o *configurationGetter) ListConfigurations(ctx context.Context) ([]*Configuration, error) {
|
||||
configurations := make([]*Configuration, 0)
|
||||
secrets := &v1.SecretList{}
|
||||
if err := o.List(ctx, secrets, client.InNamespace(constants.KubeSphereNamespace), client.MatchingLabels{constants.GenericConfigTypeLabel: ConfigTypeIdentityProvider}); err != nil {
|
||||
klog.Errorf("failed to list secrets: %v", err)
|
||||
return nil, err
|
||||
if err := o.List(ctx, secrets, client.InNamespace(constants.KubeSphereNamespace),
|
||||
client.MatchingLabels{constants.GenericConfigTypeLabel: ConfigTypeIdentityProvider}); err != nil {
|
||||
return nil, fmt.Errorf("failed to list secrets: %s", err)
|
||||
}
|
||||
for _, secret := range secrets.Items {
|
||||
if secret.Type != SecretTypeIdentityProvider {
|
||||
continue
|
||||
}
|
||||
if c, err := UnmarshalFrom(&secret); err != nil {
|
||||
if config, err := UnmarshalFrom(&secret); err != nil {
|
||||
klog.Errorf("failed to unmarshal secret data: %s", err)
|
||||
continue
|
||||
} else {
|
||||
configurations = append(configurations, c)
|
||||
configurations = append(configurations, config)
|
||||
}
|
||||
}
|
||||
return configurations, nil
|
||||
@@ -93,23 +105,25 @@ func (o *configurationGetter) ListConfigurations(ctx context.Context) ([]*Config
|
||||
func (o *configurationGetter) GetConfiguration(ctx context.Context, name string) (*Configuration, error) {
|
||||
configurations, err := o.ListConfigurations(ctx)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to list identity providers: %v", err)
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to list configurations: %s", err)
|
||||
}
|
||||
for _, c := range configurations {
|
||||
if c.Name == name {
|
||||
return c, nil
|
||||
for _, config := range configurations {
|
||||
if config.Name == name {
|
||||
if config.Disabled {
|
||||
return nil, IdentityProviderIsDisabled
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrorIdentityProviderNotFound
|
||||
}
|
||||
|
||||
func UnmarshalFrom(secret *v1.Secret) (*Configuration, error) {
|
||||
c := &Configuration{}
|
||||
if err := yaml.Unmarshal(secret.Data[SecretDataKey], c); err != nil {
|
||||
return nil, err
|
||||
config := &Configuration{}
|
||||
if err := yaml.Unmarshal(secret.Data[SecretDataKey], config); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal secret data: %s", err)
|
||||
}
|
||||
return c, nil
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func IsIdentityProviderConfiguration(secret *v1.Secret) bool {
|
||||
|
||||
@@ -268,34 +268,6 @@ func (r *Reconciler) reconcileUserStatus(ctx context.Context, user *iamv1beta1.U
|
||||
return nil
|
||||
}
|
||||
|
||||
if user.Spec.EncryptedPassword == "" {
|
||||
if user.Labels[iamv1beta1.IdentifyProviderLabel] != "" {
|
||||
// mapped user from another identity provider always active until disabled
|
||||
if user.Status.State != iamv1beta1.UserActive {
|
||||
user.Status = iamv1beta1.UserStatus{
|
||||
State: iamv1beta1.UserActive,
|
||||
LastTransitionTime: &metav1.Time{Time: time.Now()},
|
||||
}
|
||||
if err := r.Update(ctx, user, &client.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// empty password is not allowed for normal user
|
||||
if user.Status.State != iamv1beta1.UserDisabled {
|
||||
user.Status = iamv1beta1.UserStatus{
|
||||
State: iamv1beta1.UserDisabled,
|
||||
LastTransitionTime: &metav1.Time{Time: time.Now()},
|
||||
}
|
||||
if err := r.Update(ctx, user, &client.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// skip auth limit check
|
||||
return nil
|
||||
}
|
||||
|
||||
// becomes active after password encrypted
|
||||
if user.Status.State == "" && isEncrypted(user.Spec.EncryptedPassword) {
|
||||
user.Status = iamv1beta1.UserStatus{
|
||||
|
||||
@@ -144,11 +144,10 @@ func (h *handler) CreateUser(req *restful.Request, resp *restful.Response) {
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
if user.Labels == nil {
|
||||
if user.Annotations == nil {
|
||||
user.Labels = make(map[string]string)
|
||||
}
|
||||
user.Labels[iamv1beta1.IdentifyProviderLabel] = extra[iamv1beta1.ExtraIdentityProvider][0]
|
||||
user.Labels[iamv1beta1.OriginUIDLabel] = extra[iamv1beta1.ExtraUID][0]
|
||||
user.Annotations[fmt.Sprintf("%s.%s", iamv1beta1.IdentityProviderAnnotation, extra[iamv1beta1.ExtraIdentityProvider][0])] = extra[iamv1beta1.ExtraUID][0]
|
||||
delete(user.Annotations, iamv1beta1.GlobalRoleAnnotation)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog/v2"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
@@ -530,7 +529,7 @@ func (h *handler) refreshTokenGrant(req *restful.Request, response *restful.Resp
|
||||
idp := authenticated.GetExtra()[iamv1beta1.ExtraIdentityProvider][0]
|
||||
uid := authenticated.GetExtra()[iamv1beta1.ExtraUID][0]
|
||||
queryParam := query.New()
|
||||
queryParam.LabelSelector = labels.SelectorFromSet(labels.Set{iamv1beta1.IdentifyProviderLabel: idp, iamv1beta1.OriginUIDLabel: uid}).String()
|
||||
queryParam.Filters = map[query.Field]query.Value{query.FieldAnnotation: query.Value(fmt.Sprintf("%s.%s=%s", iamv1beta1.IdentityProviderAnnotation, idp, uid))}
|
||||
users, err := h.im.ListUsers(queryParam)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to list users: %s", err)
|
||||
|
||||
@@ -13,7 +13,10 @@ import (
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
authuser "k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog/v2"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
)
|
||||
@@ -54,17 +57,56 @@ func newRreRegistrationUser(idp string, identity identityprovider.Identity) auth
|
||||
}
|
||||
}
|
||||
|
||||
func newMappedUser(idp string, identity identityprovider.Identity) *iamv1beta1.User {
|
||||
// username convert
|
||||
username := strings.ToLower(identity.GetUsername())
|
||||
return &iamv1beta1.User{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: username,
|
||||
Labels: map[string]string{
|
||||
iamv1beta1.IdentifyProviderLabel: idp,
|
||||
iamv1beta1.OriginUIDLabel: identity.GetUserID(),
|
||||
},
|
||||
},
|
||||
Spec: iamv1beta1.UserSpec{Email: identity.GetEmail()},
|
||||
func authByIdentityProvider(ctx context.Context, client client.Client, mapper UserMapper, providerConfig *identityprovider.Configuration, identity identityprovider.Identity) (authuser.Info, error) {
|
||||
mappedUser, err := mapper.FindMappedUser(ctx, providerConfig.Name, identity.GetUserID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find mapped user: %s", err)
|
||||
}
|
||||
|
||||
if mappedUser.Name == "" {
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodLookup {
|
||||
return nil, fmt.Errorf("failed to find mapped user: %s", identity.GetUserID())
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodManual {
|
||||
return newRreRegistrationUser(providerConfig.Name, identity), nil
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodAuto {
|
||||
mappedUser := iamv1beta1.User{ObjectMeta: metav1.ObjectMeta{Name: strings.ToLower(identity.GetUsername())}}
|
||||
|
||||
op, err := ctrl.CreateOrUpdate(ctx, client, &mappedUser, func() error {
|
||||
|
||||
if mappedUser.Status.State == iamv1beta1.UserDisabled {
|
||||
return AccountIsNotActiveError
|
||||
}
|
||||
|
||||
if mappedUser.Annotations == nil {
|
||||
mappedUser.Annotations = make(map[string]string)
|
||||
}
|
||||
mappedUser.Annotations[fmt.Sprintf("%s.%s", iamv1beta1.IdentityProviderAnnotation, providerConfig.Name)] = identity.GetUserID()
|
||||
mappedUser.Status.State = iamv1beta1.UserActive
|
||||
if identity.GetEmail() != "" {
|
||||
mappedUser.Spec.Email = identity.GetEmail()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create or update user %s, error: %v", mappedUser.Name, err)
|
||||
}
|
||||
|
||||
klog.V(4).Infof("user %s has been updated successfully, operation: %s", mappedUser.Name, op)
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid mapping method found %s", providerConfig.MappingMethod)
|
||||
}
|
||||
|
||||
if mappedUser.Status.State == iamv1beta1.UserDisabled {
|
||||
return nil, AccountIsNotActiveError
|
||||
}
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog/v2"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
@@ -37,12 +36,10 @@ func (l *loginRecorder) RecordLogin(ctx context.Context, username string, loginT
|
||||
// only for existing accounts, solve the problem of huge entries
|
||||
user, err := l.userMapper.Find(ctx, username)
|
||||
if err != nil {
|
||||
// ignore not found error
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Error(err)
|
||||
return err
|
||||
return fmt.Errorf("failed to find user %s", username)
|
||||
}
|
||||
if user.Name == "" {
|
||||
return nil
|
||||
}
|
||||
record := &iamv1beta1.LoginRecord{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
|
||||
@@ -7,51 +7,74 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type UserMapper interface {
|
||||
Find(ctx context.Context, username string) (iamv1beta1.User, error)
|
||||
FindMappedUser(ctx context.Context, idp, uid string) (iamv1beta1.User, error)
|
||||
}
|
||||
|
||||
type userMapper struct {
|
||||
cache runtimeclient.Reader
|
||||
}
|
||||
|
||||
// Find returns the user associated with the username or email
|
||||
func (u *userMapper) Find(ctx context.Context, username string) (*iamv1beta1.User, error) {
|
||||
user := &iamv1beta1.User{}
|
||||
if _, err := mail.ParseAddress(username); err != nil {
|
||||
return user, u.cache.Get(ctx, types.NamespacedName{Name: username}, user)
|
||||
func (u *userMapper) Find(ctx context.Context, username string) (iamv1beta1.User, error) {
|
||||
user, err := u.getUserByUsernameOrEmail(ctx, username)
|
||||
if err != nil {
|
||||
return iamv1beta1.User{}, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// TODO cache with index
|
||||
userList := &iamv1beta1.UserList{}
|
||||
if err := u.cache.List(ctx, userList); err != nil {
|
||||
return nil, err
|
||||
func (u *userMapper) FindMappedUser(ctx context.Context, idp, uid string) (iamv1beta1.User, error) {
|
||||
user, err := u.getUserByAnnotation(ctx, fmt.Sprintf("%s.%s", iamv1beta1.IdentityProviderAnnotation, idp), uid)
|
||||
if err != nil {
|
||||
return iamv1beta1.User{}, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
for _, user := range userList.Items {
|
||||
if user.Spec.Email == username {
|
||||
return &user, nil
|
||||
func (u *userMapper) getUserByUsernameOrEmail(ctx context.Context, username string) (iamv1beta1.User, error) {
|
||||
if _, err := mail.ParseAddress(username); err == nil {
|
||||
return u.getUserByEmail(ctx, username)
|
||||
}
|
||||
return u.getUserByName(ctx, username)
|
||||
}
|
||||
|
||||
func (u *userMapper) getUserByName(ctx context.Context, username string) (iamv1beta1.User, error) {
|
||||
user := iamv1beta1.User{}
|
||||
err := u.cache.Get(ctx, types.NamespacedName{Name: username}, &user)
|
||||
return user, runtimeclient.IgnoreNotFound(err)
|
||||
}
|
||||
|
||||
func (u *userMapper) getUserByEmail(ctx context.Context, email string) (iamv1beta1.User, error) {
|
||||
users := &iamv1beta1.UserList{}
|
||||
if err := u.cache.List(ctx, users); err != nil {
|
||||
return iamv1beta1.User{}, err
|
||||
}
|
||||
for _, user := range users.Items {
|
||||
if user.Spec.Email == email {
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFound(iamv1beta1.Resource("user"), username)
|
||||
return iamv1beta1.User{}, nil
|
||||
}
|
||||
|
||||
// FindMappedUser returns the user which mapped to the identity
|
||||
func (u *userMapper) FindMappedUser(ctx context.Context, idp, uid string) (*iamv1beta1.User, error) {
|
||||
userList := &iamv1beta1.UserList{}
|
||||
if err := u.cache.List(ctx, userList, runtimeclient.MatchingLabels{
|
||||
iamv1beta1.IdentifyProviderLabel: idp,
|
||||
iamv1beta1.OriginUIDLabel: uid,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
func (u *userMapper) getUserByAnnotation(ctx context.Context, annotation, value string) (iamv1beta1.User, error) {
|
||||
users := &iamv1beta1.UserList{}
|
||||
if err := u.cache.List(ctx, users); err != nil {
|
||||
return iamv1beta1.User{}, err
|
||||
}
|
||||
if len(userList.Items) != 1 {
|
||||
return nil, nil
|
||||
for _, user := range users.Items {
|
||||
if user.Annotations[annotation] == value {
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
return &userList.Items[0], nil
|
||||
return iamv1beta1.User{}, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
authuser "k8s.io/apiserver/pkg/authentication/user"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
@@ -19,15 +18,15 @@ import (
|
||||
|
||||
type oauthAuthenticator struct {
|
||||
client runtimeclient.Client
|
||||
userGetter *userMapper
|
||||
userMapper UserMapper
|
||||
idpConfigurationGetter identityprovider.ConfigurationGetter
|
||||
}
|
||||
|
||||
func NewOAuthAuthenticator(cacheClient runtimeclient.Client) OAuthAuthenticator {
|
||||
func NewOAuthAuthenticator(client runtimeclient.Client) OAuthAuthenticator {
|
||||
authenticator := &oauthAuthenticator{
|
||||
client: cacheClient,
|
||||
userGetter: &userMapper{cache: cacheClient},
|
||||
idpConfigurationGetter: identityprovider.NewConfigurationGetter(cacheClient),
|
||||
client: client,
|
||||
userMapper: &userMapper{cache: client},
|
||||
idpConfigurationGetter: identityprovider.NewConfigurationGetter(client),
|
||||
}
|
||||
return authenticator
|
||||
}
|
||||
@@ -49,36 +48,5 @@ func (o *oauthAuthenticator) Authenticate(ctx context.Context, provider string,
|
||||
return nil, fmt.Errorf("failed to exchange identity for %s, error: %v", provider, err)
|
||||
}
|
||||
|
||||
mappedUser, err := o.userGetter.FindMappedUser(ctx, providerConfig.Name, identity.GetUserID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find mapped user for %s, error: %v", provider, err)
|
||||
}
|
||||
|
||||
if mappedUser == nil {
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodLookup {
|
||||
return nil, fmt.Errorf("failed to find mapped user: %s", identity.GetUserID())
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodManual {
|
||||
return newRreRegistrationUser(providerConfig.Name, identity), nil
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodAuto {
|
||||
mappedUser = newMappedUser(providerConfig.Name, identity)
|
||||
|
||||
if err = o.client.Create(ctx, mappedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid mapping method found %s", providerConfig.MappingMethod)
|
||||
}
|
||||
|
||||
if mappedUser.Status.State == iamv1beta1.UserDisabled {
|
||||
return nil, AccountIsNotActiveError
|
||||
}
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
return authByIdentityProvider(ctx, o.client, o.userMapper, providerConfig, identity)
|
||||
}
|
||||
|
||||
@@ -51,10 +51,7 @@ func Test_oauthAuthenticator_Authenticate(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
marshal, err := yaml.Marshal(fakeIDP)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
marshal, _ := yaml.Marshal(fakeIDP)
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -71,8 +68,8 @@ func Test_oauthAuthenticator_Authenticate(t *testing.T) {
|
||||
}
|
||||
|
||||
fakeCache := informertest.FakeInformers{Scheme: scheme.Scheme}
|
||||
err = fakeCache.Start(context.Background())
|
||||
if err != nil {
|
||||
|
||||
if err := fakeCache.Start(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeSecretInformer, err := fakeCache.FakeInformerFor(context.Background(), &v1.Secret{})
|
||||
@@ -177,9 +174,8 @@ func newUser(username string, uid string, idp string) *iamv1beta1.User {
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: username,
|
||||
Labels: map[string]string{
|
||||
iamv1beta1.IdentifyProviderLabel: idp,
|
||||
iamv1beta1.OriginUIDLabel: uid,
|
||||
Annotations: map[string]string{
|
||||
fmt.Sprintf("%s.%s", iamv1beta1.IdentityProviderAnnotation, idp): uid,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
)
|
||||
|
||||
type passwordAuthenticator struct {
|
||||
userGetter *userMapper
|
||||
userMapper UserMapper
|
||||
client runtimeclient.Client
|
||||
authOptions *authentication.Options
|
||||
identityProviderConfigurationGetter identityprovider.ConfigurationGetter
|
||||
@@ -29,7 +29,7 @@ type passwordAuthenticator struct {
|
||||
func NewPasswordAuthenticator(cacheClient runtimeclient.Client, options *authentication.Options) PasswordAuthenticator {
|
||||
passwordAuthenticator := &passwordAuthenticator{
|
||||
client: cacheClient,
|
||||
userGetter: &userMapper{cache: cacheClient},
|
||||
userMapper: &userMapper{cache: cacheClient},
|
||||
identityProviderConfigurationGetter: identityprovider.NewConfigurationGetter(cacheClient),
|
||||
authOptions: options,
|
||||
}
|
||||
@@ -49,36 +49,28 @@ func (p *passwordAuthenticator) Authenticate(ctx context.Context, provider, user
|
||||
|
||||
// authByKubeSphere authenticate by the kubesphere user
|
||||
func (p *passwordAuthenticator) authByKubeSphere(ctx context.Context, username, password string) (authuser.Info, error) {
|
||||
user, err := p.userGetter.Find(ctx, username)
|
||||
user, err := p.userMapper.Find(ctx, username)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, IncorrectPasswordError
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find user: %s", err)
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
if user.Name == "" {
|
||||
return nil, IncorrectPasswordError
|
||||
}
|
||||
|
||||
// check user status
|
||||
if user.Status.State != iamv1beta1.UserActive {
|
||||
if user.Status.State == iamv1beta1.UserAuthLimitExceeded {
|
||||
return nil, RateLimitExceededError
|
||||
} else {
|
||||
return nil, AccountIsNotActiveError
|
||||
}
|
||||
switch user.Status.State {
|
||||
case iamv1beta1.UserAuthLimitExceeded:
|
||||
return nil, RateLimitExceededError
|
||||
case iamv1beta1.UserActive:
|
||||
break
|
||||
default:
|
||||
return nil, AccountIsNotActiveError
|
||||
}
|
||||
|
||||
// if the password is not empty, means that the password has been reset, even if the user was mapping from IDP
|
||||
if user.Spec.EncryptedPassword == "" {
|
||||
if user.Spec.EncryptedPassword == "" || PasswordVerify(user.Spec.EncryptedPassword, password) != nil {
|
||||
return nil, IncorrectPasswordError
|
||||
}
|
||||
|
||||
if err = PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := &authuser.DefaultInfo{
|
||||
Name: user.Name,
|
||||
Groups: user.Spec.Groups,
|
||||
@@ -111,41 +103,10 @@ func (p *passwordAuthenticator) authByProvider(ctx context.Context, provider, us
|
||||
if errors.IsUnauthorized(err) {
|
||||
return nil, IncorrectPasswordError
|
||||
}
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to authenticate by identity provider %s: %s", provider, err)
|
||||
}
|
||||
|
||||
mappedUser, err := p.userGetter.FindMappedUser(ctx, provider, identity.GetUserID())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find mapped user: %s", err)
|
||||
}
|
||||
|
||||
if mappedUser == nil {
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodLookup {
|
||||
return nil, fmt.Errorf("failed to find mapped user: %s", identity.GetUserID())
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodManual {
|
||||
return newRreRegistrationUser(providerConfig.Name, identity), nil
|
||||
}
|
||||
|
||||
if providerConfig.MappingMethod == identityprovider.MappingMethodAuto {
|
||||
mappedUser = newMappedUser(providerConfig.Name, identity)
|
||||
|
||||
if err = p.client.Create(ctx, mappedUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid mapping method found %s", providerConfig.MappingMethod)
|
||||
}
|
||||
|
||||
if mappedUser.Status.State == iamv1beta1.UserDisabled {
|
||||
return nil, AccountIsNotActiveError
|
||||
}
|
||||
|
||||
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, nil
|
||||
return authByIdentityProvider(ctx, p.client, p.userMapper, providerConfig, identity)
|
||||
}
|
||||
|
||||
func PasswordVerify(encryptedPassword, password string) error {
|
||||
|
||||
@@ -10,30 +10,24 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
runtimefakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/scheme"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/server/options"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gopkg.in/yaml.v3"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
authuser "k8s.io/apiserver/pkg/authentication/user"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
|
||||
runtimefakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/scheme"
|
||||
"kubesphere.io/kubesphere/pkg/server/options"
|
||||
)
|
||||
|
||||
func TestEncryptPassword(t *testing.T) {
|
||||
@@ -112,10 +106,7 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
marshal1, err := yaml.Marshal(fakepwd1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
marshal1, _ := yaml.Marshal(fakepwd1)
|
||||
|
||||
fakepwd1Secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -131,10 +122,8 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
|
||||
Type: identityprovider.SecretTypeIdentityProvider,
|
||||
}
|
||||
|
||||
marshal2, err := yaml.Marshal(fakepwd2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
marshal2, _ := yaml.Marshal(fakepwd2)
|
||||
|
||||
fakepwd2Secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-fake-idp2",
|
||||
@@ -149,10 +138,8 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
|
||||
Type: identityprovider.SecretTypeIdentityProvider,
|
||||
}
|
||||
|
||||
marshal3, err := yaml.Marshal(fakepwd3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
marshal3, _ := yaml.Marshal(fakepwd3)
|
||||
|
||||
fakepwd3Secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-fake-idp3",
|
||||
@@ -180,8 +167,8 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
|
||||
Build()
|
||||
|
||||
fakeCache := informertest.FakeInformers{Scheme: scheme.Scheme}
|
||||
err = fakeCache.Start(context.Background())
|
||||
if err != nil {
|
||||
|
||||
if err := fakeCache.Start(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeSecretInformer, err := fakeCache.FakeInformerFor(context.Background(), &v1.Secret{})
|
||||
|
||||
@@ -54,8 +54,7 @@ const (
|
||||
ScopeLabel = "iam.kubesphere.io/scope"
|
||||
UserReferenceLabel = "iam.kubesphere.io/user-ref"
|
||||
RoleReferenceLabel = "iam.kubesphere.io/role-ref"
|
||||
IdentifyProviderLabel = "iam.kubesphere.io/identify-provider"
|
||||
OriginUIDLabel = "iam.kubesphere.io/origin-uid"
|
||||
IdentityProviderAnnotation = "iam.kubesphere.io/identity-provider"
|
||||
ServiceAccountReferenceLabel = "iam.kubesphere.io/serviceaccount-ref"
|
||||
FieldEmail = "email"
|
||||
ExtraEmail = FieldEmail
|
||||
@@ -71,7 +70,6 @@ const (
|
||||
NamespaceAdmin = "admin"
|
||||
ClusterAdmin = "cluster-admin"
|
||||
PreRegistrationUser = "system:pre-registration"
|
||||
OTPAuthRequiredUser = "system:otp-auth-required"
|
||||
ResourcePluralGroup = "groups"
|
||||
GroupReferenceLabel = "iam.kubesphere.io/group-ref"
|
||||
GroupParent = "iam.kubesphere.io/group-parent"
|
||||
|
||||
Reference in New Issue
Block a user