From cb947ce5051fff8b8162e96195cc8a0a0a83873b Mon Sep 17 00:00:00 2001 From: hongming Date: Wed, 15 Sep 2021 11:20:05 +0800 Subject: [PATCH] Support skip information reconfirm when using external IDP Signed-off-by: hongming --- .../authentication/oauth/oauth_options.go | 8 ++- .../oauth/oauth_options_test.go | 2 +- pkg/controller/user/user_webhook.go | 4 ++ pkg/models/auth/authenticator.go | 49 ++++++++++++++++--- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/pkg/apiserver/authentication/oauth/oauth_options.go b/pkg/apiserver/authentication/oauth/oauth_options.go index eb942e8b4..cf84e858c 100644 --- a/pkg/apiserver/authentication/oauth/oauth_options.go +++ b/pkg/apiserver/authentication/oauth/oauth_options.go @@ -37,7 +37,8 @@ const ( GrantHandlerPrompt GrantHandlerType = "prompt" // GrantHandlerDeny auto-denies client authorization grant requests GrantHandlerDeny GrantHandlerType = "deny" - // MappingMethodAuto The default value.The user will automatically create and mapping when login successful. + // MappingMethodAuto The default value. + // The user will automatically create and mapping when login successful. // Fails if a user with that username is already mapped to another identity. MappingMethodAuto MappingMethod = "auto" // MappingMethodLookup Looks up an existing identity, user identity mapping, and user, but does not automatically @@ -141,6 +142,11 @@ type IdentityProviderOptions struct { // - mixed: A user entity can be mapped with multiple identifyProvider. MappingMethod MappingMethod `json:"mappingMethod" yaml:"mappingMethod"` + // DisableLoginConfirmation means that when the user login successfully, + // reconfirm the account information is not required. + // Username from IDP must math [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* + DisableLoginConfirmation bool `json:"disableLoginConfirmation" yaml:"disableLoginConfirmation"` + // The type of identify provider // OpenIDIdentityProvider LDAPIdentityProvider GitHubIdentityProvider Type string `json:"type" yaml:"type"` diff --git a/pkg/apiserver/authentication/oauth/oauth_options_test.go b/pkg/apiserver/authentication/oauth/oauth_options_test.go index 70f43ac07..a279d6133 100644 --- a/pkg/apiserver/authentication/oauth/oauth_options_test.go +++ b/pkg/apiserver/authentication/oauth/oauth_options_test.go @@ -136,7 +136,7 @@ identityProviders: if err := yaml.Unmarshal([]byte(config), &options); err != nil { t.Error(err) } - expected := `{"identityProviders":[{"name":"ldap","mappingMethod":"auto","type":"LDAPIdentityProvider","provider":{"host":"xxxx.sn.mynetname.net:389","loginAttribute":"uid","mailAttribute":"mail","managerDN":"uid=root,cn=users,dc=xxxx,dc=sn,dc=mynetname,dc=net","userSearchBase":"dc=xxxx,dc=sn,dc=mynetname,dc=net"}},{"name":"github","mappingMethod":"mixed","type":"GitHubIdentityProvider","provider":{"clientID":"xxxxxx","endpoint":{"authURL":"https://github.com/login/oauth/authorize","tokenURL":"https://github.com/login/oauth/access_token"},"redirectURL":"https://ks-console/oauth/redirect","scopes":["user"]}}],"accessTokenMaxAge":3600000000000,"accessTokenInactivityTimeout":1800000000000}` + expected := `{"identityProviders":[{"name":"ldap","mappingMethod":"auto","disableLoginConfirmation":false,"type":"LDAPIdentityProvider","provider":{"host":"xxxx.sn.mynetname.net:389","loginAttribute":"uid","mailAttribute":"mail","managerDN":"uid=root,cn=users,dc=xxxx,dc=sn,dc=mynetname,dc=net","userSearchBase":"dc=xxxx,dc=sn,dc=mynetname,dc=net"}},{"name":"github","mappingMethod":"mixed","disableLoginConfirmation":false,"type":"GitHubIdentityProvider","provider":{"clientID":"xxxxxx","endpoint":{"authURL":"https://github.com/login/oauth/authorize","tokenURL":"https://github.com/login/oauth/access_token"},"redirectURL":"https://ks-console/oauth/redirect","scopes":["user"]}}],"accessTokenMaxAge":3600000000000,"accessTokenInactivityTimeout":1800000000000}` output, _ := json.Marshal(options) if expected != string(output) { t.Errorf("expected: %s, but got: %s", expected, output) diff --git a/pkg/controller/user/user_webhook.go b/pkg/controller/user/user_webhook.go index bd54e7bff..a822abccd 100644 --- a/pkg/controller/user/user_webhook.go +++ b/pkg/controller/user/user_webhook.go @@ -58,6 +58,10 @@ func (a *EmailValidator) Handle(ctx context.Context, req admission.Request) admi } func emailAlreadyExist(users v1alpha2.UserList, user *v1alpha2.User) bool { + // empty email is allowed + if user.Spec.Email == "" { + return false + } for _, exist := range users.Items { if exist.Spec.Email == user.Spec.Email && exist.Name != user.Name { return true diff --git a/pkg/models/auth/authenticator.go b/pkg/models/auth/authenticator.go index 6c63bc029..bbdd38224 100644 --- a/pkg/models/auth/authenticator.go +++ b/pkg/models/auth/authenticator.go @@ -19,8 +19,12 @@ package auth import ( + "context" "fmt" "net/mail" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "golang.org/x/crypto/bcrypt" @@ -113,17 +117,27 @@ func (p *passwordAuthenticator) Authenticate(username, password string) (authuse return nil, providerOptions.Name, err } linkedAccount, err := p.userGetter.findLinkedAccount(providerOptions.Name, authenticated.GetUserID()) + if err != nil { + return nil, providerOptions.Name, err + } // using this method requires you to manually provision users. if providerOptions.MappingMethod == oauth.MappingMethodLookup && linkedAccount == nil { continue } + // the user will automatically create and mapping when login successful. + if linkedAccount == nil && providerOptions.MappingMethod == oauth.MappingMethodAuto { + if !providerOptions.DisableLoginConfirmation { + return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil + } + + linkedAccount, err = p.ksClient.IamV1alpha2().Users().Create(context.Background(), mappedUser(providerOptions.Name, authenticated), metav1.CreateOptions{}) + if err != nil { + return nil, providerOptions.Name, err + } + } if linkedAccount != nil { return &authuser.DefaultInfo{Name: linkedAccount.GetName()}, providerOptions.Name, nil } - // the user will automatically create and mapping when login successful. - if providerOptions.MappingMethod == oauth.MappingMethodAuto { - return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil - } } } @@ -183,7 +197,22 @@ func preRegistrationUser(idp string, identity identityprovider.Identity) authuse } } -func (o oauth2Authenticator) Authenticate(provider, code string) (authuser.Info, string, error) { +func mappedUser(idp string, identity identityprovider.Identity) *iamv1alpha2.User { + // username convert + username := strings.ToLower(identity.GetUsername()) + return &iamv1alpha2.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: username, + Labels: map[string]string{ + iamv1alpha2.IdentifyProviderLabel: idp, + iamv1alpha2.OriginUIDLabel: identity.GetUserID(), + }, + }, + Spec: iamv1alpha2.UserSpec{Email: identity.GetEmail()}, + } +} + +func (o *oauth2Authenticator) Authenticate(provider, code string) (authuser.Info, string, error) { providerOptions, err := o.authOptions.OAuthOptions.IdentityProviderOptions(provider) // identity provider not registered if err != nil { @@ -206,10 +235,18 @@ func (o oauth2Authenticator) Authenticate(provider, code string) (authuser.Info, klog.Error(err) return nil, "", err } + // the user will automatically create and mapping when login successful. if user == nil && providerOptions.MappingMethod == oauth.MappingMethodAuto { - return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil + if !providerOptions.DisableLoginConfirmation { + return preRegistrationUser(providerOptions.Name, authenticated), providerOptions.Name, nil + } + user, err = o.ksClient.IamV1alpha2().Users().Create(context.Background(), mappedUser(providerOptions.Name, authenticated), metav1.CreateOptions{}) + if err != nil { + return nil, providerOptions.Name, err + } } + if user != nil { return &authuser.DefaultInfo{Name: user.GetName()}, providerOptions.Name, nil }