add userinfo endpoint

Signed-off-by: hongming <hongming@kubesphere.io>
This commit is contained in:
hongming
2021-09-14 18:31:03 +08:00
parent 8c5c6a7dee
commit 97326a89b9
14 changed files with 168 additions and 122 deletions

View File

@@ -239,8 +239,8 @@ func (s *APIServer) installKubeSphereAPIs() {
userLister := s.InformerFactory.KubeSphereSharedInformerFactory().Iam().V1alpha2().Users().Lister()
urlruntime.Must(oauth.AddToContainer(s.container, imOperator,
auth.NewTokenOperator(s.CacheClient, s.Issuer, s.Config.AuthenticationOptions),
auth.NewPasswordAuthenticator(userLister, s.Config.AuthenticationOptions),
auth.NewOAuthAuthenticator(userLister, s.Config.AuthenticationOptions),
auth.NewPasswordAuthenticator(s.KubernetesClient.KubeSphere(), userLister, s.Config.AuthenticationOptions),
auth.NewOAuthAuthenticator(s.KubernetesClient.KubeSphere(), userLister, s.Config.AuthenticationOptions),
auth.NewLoginRecorder(s.KubernetesClient.KubeSphere(), userLister),
s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.Config.ServiceMeshOptions, s.container, s.KubernetesClient.Kubernetes(), s.CacheClient))
@@ -337,7 +337,9 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(userLister,
basictoken.New(basic.NewBasicAuthenticator(auth.NewPasswordAuthenticator(
s.KubernetesClient.KubeSphere(),
userLister,
s.Config.AuthenticationOptions),
loginRecorder)),
bearertoken.New(jwt.NewTokenAuthenticator(

View File

@@ -53,7 +53,7 @@ endpoint:
userInfoUrl: "https://xxxxx.login.aliyunidaas.com/api/bff/v1.2/oauth2/userinfo"
authURL: "https://xxxx.login.aliyunidaas.com/oauth/authorize"
tokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token"
redirectURL: "https://console.kubesphere.io/oauth/redirect/idaas"
redirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas"
scopes:
- read
`)},
@@ -65,7 +65,7 @@ scopes:
TokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token",
UserInfoURL: "https://xxxxx.login.aliyunidaas.com/api/bff/v1.2/oauth2/userinfo",
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/idaas",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas",
Scopes: []string{"read"},
Config: &oauth2.Config{
ClientID: "xxxx",
@@ -75,7 +75,7 @@ scopes:
TokenURL: "https://xxxx.login.aliyunidaas.com/oauth/token",
AuthStyle: oauth2.AuthStyleAutoDetect,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/idaas",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/idaas",
Scopes: []string{"read"},
},
},

View File

@@ -87,7 +87,7 @@ var _ = Describe("GitHub", func() {
configYAML := `
clientID: de6ff8bed0304e487b6e
clientSecret: 2b70536f79ec8d2939863509d05e2a71c268b9af
redirectURL: "https://console.kubesphere.io/oauth/redirect/github"
redirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github"
scopes:
- user
`
@@ -103,7 +103,7 @@ scopes:
TokenURL: tokenURL,
UserInfoURL: userInfoURL,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/github",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
Scopes: []string{"user"},
Config: &oauth2.Config{
ClientID: "de6ff8bed0304e487b6e",
@@ -112,7 +112,7 @@ scopes:
AuthURL: authURL,
TokenURL: tokenURL,
},
RedirectURL: "https://console.kubesphere.io/oauth/redirect/github",
RedirectURL: "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
Scopes: []string{"user"},
},
}
@@ -122,7 +122,7 @@ scopes:
config := oauth.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/github",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
@@ -136,7 +136,7 @@ scopes:
expected := oauth.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/github",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
@@ -147,7 +147,7 @@ scopes:
Expect(config).Should(Equal(expected))
})
It("should login successfully", func() {
url, _ := url.Parse("https://console.kubesphere.io/oauth/redirect/test?code=00000")
url, _ := url.Parse("https://ks-console.kubesphere-system.svc/oauth/redirect/test?code=00000")
req := &http.Request{URL: url}
identity, err := provider.IdentityExchangeCallback(req)
Expect(err).Should(BeNil())

View File

@@ -171,7 +171,7 @@ var _ = Describe("OIDC", func() {
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/oidc",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/oidc",
"insecureSkipVerify": true,
}
factory := oidcProviderFactory{}
@@ -181,7 +181,7 @@ var _ = Describe("OIDC", func() {
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
"redirectURL": "https://console.kubesphere.io/oauth/redirect/oidc",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/oidc",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"authURL": fmt.Sprintf("%s/authorize", oidcServer.URL),
@@ -194,7 +194,7 @@ var _ = Describe("OIDC", func() {
Expect(config).Should(Equal(expected))
})
It("should login successfully", func() {
url, _ := url.Parse("https://console.kubesphere.io/oauth/redirect/oidc?code=00000")
url, _ := url.Parse("https://ks-console.kubesphere-system.svc/oauth/redirect/oidc?code=00000")
req := &http.Request{URL: url}
identity, err := provider.IdentityExchangeCallback(req)
Expect(err).Should(BeNil())

View File

@@ -1,5 +1,5 @@
/*
Copyright 2020 The KubeSphere Authors.
Copyright 2021 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package oauth
import "fmt"
// The following error type is defined in https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
var (
// ErrorInvalidClient
// Client authentication failed (e.g., unknown client, no

View File

@@ -191,7 +191,7 @@ type Token struct {
type Client struct {
// The name of the OAuth client is used as the client_id parameter when making requests to <master>/oauth/authorize
// and <master>/oauth/token.
Name string
Name string `json:"name" yaml:"name,omitempty"`
// Secret is the unique secret associated with a client
Secret string `json:"-" yaml:"secret,omitempty"`
@@ -225,19 +225,7 @@ type Client struct {
var (
// AllowAllRedirectURI Allow any redirect URI if the redirectURI is defined in request
AllowAllRedirectURI = "*"
DefaultTokenMaxAge = time.Second * 86400
DefaultAccessTokenInactivityTimeout = time.Duration(0)
DefaultClients = []Client{{
Name: "default",
Secret: "kubesphere",
RespondWithChallenges: true,
RedirectURIs: []string{AllowAllRedirectURI},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
AccessTokenMaxAge: &DefaultTokenMaxAge,
AccessTokenInactivityTimeout: &DefaultAccessTokenInactivityTimeout,
}}
AllowAllRedirectURI = "*"
)
func (o *Options) OAuthClient(name string) (Client, error) {
@@ -246,11 +234,6 @@ func (o *Options) OAuthClient(name string) (Client, error) {
return found, nil
}
}
for _, defaultClient := range DefaultClients {
if defaultClient.Name == name {
return defaultClient, nil
}
}
return Client{}, ErrorClientNotFound
}

View File

@@ -19,77 +19,39 @@ package oauth
import (
"encoding/json"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"gopkg.in/yaml.v3"
)
func TestDefaultAuthOptions(t *testing.T) {
oneDay := time.Second * 86400
zero := time.Duration(0)
expect := Client{
Name: "default",
RespondWithChallenges: true,
Secret: "kubesphere",
RedirectURIs: []string{AllowAllRedirectURI},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
AccessTokenMaxAge: &oneDay,
AccessTokenInactivityTimeout: &zero,
}
options := NewOptions()
client, err := options.OAuthClient("default")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(expect, client); len(diff) != 0 {
t.Errorf("%T differ (-got, +expected), %s", expect, diff)
}
}
func TestClientResolveRedirectURL(t *testing.T) {
options := NewOptions()
defaultClient, err := options.OAuthClient("default")
if err != nil {
t.Fatal(err)
}
tests := []struct {
Name string
client Client
wantErr bool
expectURL string
}{
{
Name: "default client test",
client: defaultClient,
wantErr: false,
expectURL: "https://localhost:8080/auth/cb",
},
{
Name: "custom client test",
client: Client{
Name: "default",
RespondWithChallenges: true,
RedirectURIs: []string{"https://foo.bar.com/oauth/cb"},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
},
wantErr: true,
expectURL: "https://foo.bar.com/oauth/err",
},
{
Name: "custom client test",
client: Client{
Name: "default",
Name: "custom",
RespondWithChallenges: true,
RedirectURIs: []string{AllowAllRedirectURI, "https://foo.bar.com/oauth/cb"},
GrantMethod: GrantHandlerAuto,
ScopeRestrictions: []string{"full"},
},
wantErr: false,
expectURL: "https://foo.bar.com/oauth/err2",
expectURL: "https://foo.bar.com/oauth/cb",
},
{
Name: "custom client test",
client: Client{
Name: "custom",
RespondWithChallenges: true,
RedirectURIs: []string{"https://foo.bar.com/oauth/cb"},
GrantMethod: GrantHandlerAuto,
},
wantErr: true,
expectURL: "https://foo.bar.com/oauth/cb2",
},
}

View File

@@ -83,22 +83,30 @@ type Claims struct {
jwt.StandardClaims
// Private Claim Names
// TokenType defined the type of the token
TokenType Type `json:"token_type"`
// Username is user identity same as `sub`
Username string `json:"username"`
TokenType Type `json:"token_type,omitempty"`
// Username user identity, deprecated field
Username string `json:"username,omitempty"`
// Extra contains the additional information
Extra map[string][]string `json:"extra,omitempty"`
// Used for issuing authorization code
// Scopes can be used to request that specific sets of information be made available as Claim Values.
Scopes []string `json:"scopes,omitempty"`
// The following is well-known ID Token fields
// End-User's full name in displayable form including all name parts,
// possibly including titles and suffixes, ordered according to the End-User's locale and preferences.
Name string `json:"name,omitempty"`
// String value used to associate a Client session with an ID Token, and to mitigate replay attacks.
// The value is passed through unmodified from the Authentication Request to the ID Token.
Nonce string `json:"nonce,omitempty"`
// Scopes can be used to request that specific sets of information be made available as Claim Values.
Scopes []string `json:"scopes,omitempty"`
// End-User's preferred e-mail address.
Email string `json:"email,omitempty"`
// End-User's locale, represented as a BCP47 [RFC5646] language tag.
Locale string `json:"locale,omitempty"`
// Shorthand name by which the End-User wishes to be referred to at the RP,
PreferredUsername string `json:"preferred_username,omitempty"`
// Extra contains the additional information
Extra map[string][]string `json:"extra,omitempty"`
}
type issuer struct {
@@ -128,6 +136,9 @@ func (s *issuer) IssueTo(request *IssueRequest) (string, error) {
if len(request.Audience) > 0 {
claims.Audience = request.Audience
}
if request.Name != "" {
claims.Name = request.Name
}
if request.Nonce != "" {
claims.Nonce = request.Nonce
}

View File

@@ -18,13 +18,15 @@ package oauth
import (
"fmt"
"gopkg.in/square/go-jose.v2"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"net/http"
"net/url"
"strings"
"time"
"gopkg.in/square/go-jose.v2"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"github.com/form3tech-oss/jwt-go"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
@@ -443,7 +445,7 @@ func (h *handler) passwordGrant(username string, password string, req *restful.R
response.WriteHeaderAndEntity(http.StatusBadRequest, oauth.NewInvalidGrant(err))
return
case auth.RateLimitExceededError:
response.WriteHeaderAndEntity(http.StatusBadRequest, oauth.NewInvalidGrant(err))
response.WriteHeaderAndEntity(http.StatusTooManyRequests, oauth.NewInvalidGrant(err))
return
default:
response.WriteHeaderAndEntity(http.StatusInternalServerError, oauth.NewServerError(err))
@@ -602,6 +604,7 @@ func (h *handler) codeGrant(req *restful.Request, response *restful.Response) {
},
Nonce: authorizeContext.Nonce,
TokenType: token.IDToken,
Name: authorizeContext.User.GetName(),
},
ExpiresIn: h.options.OAuthOptions.AccessTokenMaxAge + h.options.OAuthOptions.AccessTokenInactivityTimeout,
}
@@ -655,3 +658,28 @@ func (h *handler) logout(req *restful.Request, resp *restful.Response) {
resp.Header().Set("Content-Type", "text/plain")
http.Redirect(resp, req.Request, redirectURL.String(), http.StatusFound)
}
// userinfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the authenticated End-User.
func (h *handler) userinfo(req *restful.Request, response *restful.Response) {
authenticated, _ := request.UserFrom(req.Request.Context())
if authenticated == nil || authenticated.GetName() == user.Anonymous {
response.WriteHeaderAndEntity(http.StatusUnauthorized, oauth.ErrorLoginRequired)
return
}
detail, err := h.im.DescribeUser(authenticated.GetName())
if err != nil {
response.WriteHeaderAndEntity(http.StatusInternalServerError, oauth.NewServerError(err))
return
}
result := token.Claims{
StandardClaims: jwt.StandardClaims{
Subject: detail.Name,
},
Name: detail.Name,
Email: detail.Spec.Email,
Locale: detail.Spec.Lang,
PreferredUsername: detail.Name,
}
response.WriteEntity(result)
}

View File

@@ -57,6 +57,8 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface,
Doc("The OpenID Provider's configuration information can be retrieved."))
ws.Route(ws.GET("/keys").To(handler.keys).
Doc("OP's JSON Web Key Set [JWK] document."))
ws.Route(ws.GET("/userinfo").To(handler.userinfo).
Doc("UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the authenticated End-User."))
// Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
@@ -100,15 +102,20 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface,
To(handler.authorize).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
// https://tools.ietf.org/html/rfc6749#section-4.3
// https://datatracker.ietf.org/doc/html/rfc6749#section-3.2
ws.Route(ws.POST("/token").
Consumes(contentTypeFormData).
Doc("The resource owner password credentials grant type is suitable in\n"+
"cases where the resource owner has a trust relationship with the\n"+
"client, such as the device operating system or a highly privileged application.").
Param(ws.FormParameter("grant_type", "Value MUST be set to \"password\".").Required(true)).
Param(ws.FormParameter("username", "The resource owner username.").Required(true)).
Param(ws.FormParameter("password", "The resource owner password.").Required(true)).
Param(ws.FormParameter("grant_type", "OAuth defines four grant types: "+
"authorization code, implicit, resource owner password credentials, and client credentials.").
Required(true)).
Param(ws.FormParameter("client_id", "Valid client credential.").Required(true)).
Param(ws.FormParameter("client_secret", "Valid client credential.").Required(true)).
Param(ws.FormParameter("username", "The resource owner username.").Required(false)).
Param(ws.FormParameter("password", "The resource owner password.").Required(false)).
Param(ws.FormParameter("code", "Valid authorization code.").Required(false)).
To(handler.token).
Returns(http.StatusOK, http.StatusText(http.StatusOK), &oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))

View File

@@ -23,6 +23,9 @@ import (
"fmt"
"net/http"
"net/mail"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
@@ -68,6 +71,21 @@ func preRegistrationUser(idp string, identity identityprovider.Identity) authuse
}
}
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()},
}
}
// findUser returns the user associated with the username or email
func (u *userGetter) findUser(username string) (*iamv1alpha2.User, error) {
if _, err := mail.ParseAddress(username); err != nil {

View File

@@ -22,6 +22,10 @@ import (
"context"
"net/http"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"k8s.io/apimachinery/pkg/api/errors"
@@ -35,49 +39,60 @@ import (
)
type oauthAuthenticator struct {
*userGetter
options *authentication.Options
ksClient kubesphere.Interface
userGetter *userGetter
options *authentication.Options
}
func NewOAuthAuthenticator(userLister iamv1alpha2listers.UserLister,
func NewOAuthAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authentication.Options) OAuthAuthenticator {
authenticator := &oauthAuthenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
options: options,
}
return authenticator
}
func (o oauthAuthenticator) Authenticate(ctx context.Context, provider string, req *http.Request) (authuser.Info, string, error) {
options, err := o.options.OAuthOptions.IdentityProviderOptions(provider)
func (o *oauthAuthenticator) Authenticate(_ context.Context, provider string, req *http.Request) (authuser.Info, string, error) {
providerOptions, err := o.options.OAuthOptions.IdentityProviderOptions(provider)
// identity provider not registered
if err != nil {
klog.Error(err)
return nil, "", err
}
identityProvider, err := identityprovider.GetOAuthProvider(options.Name)
oauthIdentityProvider, err := identityprovider.GetOAuthProvider(providerOptions.Name)
if err != nil {
klog.Error(err)
return nil, "", err
}
identity, err := identityProvider.IdentityExchangeCallback(req)
authenticated, err := oauthIdentityProvider.IdentityExchangeCallback(req)
if err != nil {
klog.Error(err)
return nil, "", err
}
mappedUser, err := o.findMappedUser(options.Name, identity.GetUserID())
if mappedUser == nil && options.MappingMethod == oauth.MappingMethodLookup {
user, err := o.userGetter.findMappedUser(providerOptions.Name, authenticated.GetUserID())
if user == nil && providerOptions.MappingMethod == oauth.MappingMethodLookup {
klog.Error(err)
return nil, "", err
}
// the user will automatically create and mapping when login successful.
if mappedUser == nil && options.MappingMethod == oauth.MappingMethodAuto {
return preRegistrationUser(options.Name, identity), options.Name, nil
}
if mappedUser != nil {
return &authuser.DefaultInfo{Name: mappedUser.GetName()}, options.Name, nil
if user == nil && providerOptions.MappingMethod == oauth.MappingMethodAuto {
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
}
}
return nil, "", errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularUser), identity.GetUsername())
if user != nil {
return &authuser.DefaultInfo{Name: user.GetName()}, providerOptions.Name, nil
}
return nil, "", errors.NewNotFound(iamv1alpha2.Resource("user"), authenticated.GetUsername())
}

View File

@@ -88,6 +88,7 @@ func Test_oauthAuthenticator_Authenticate(t *testing.T) {
{
name: "Should successfully",
oauthAuthenticator: NewOAuthAuthenticator(
nil,
ksInformerFactory.Iam().V1alpha2().Users().Lister(),
oauthOptions,
),
@@ -105,6 +106,7 @@ func Test_oauthAuthenticator_Authenticate(t *testing.T) {
{
name: "Should successfully",
oauthAuthenticator: NewOAuthAuthenticator(
nil,
ksInformerFactory.Iam().V1alpha2().Users().Lister(),
oauthOptions,
),

View File

@@ -21,6 +21,10 @@ package auth
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"golang.org/x/crypto/bcrypt"
@@ -36,20 +40,23 @@ import (
)
type passwordAuthenticator struct {
ksClient kubesphere.Interface
userGetter *userGetter
authOptions *authentication.Options
}
func NewPasswordAuthenticator(userLister iamv1alpha2listers.UserLister,
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authentication.Options) PasswordAuthenticator {
passwordAuthenticator := &passwordAuthenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
authOptions: options,
}
return passwordAuthenticator
}
func (p *passwordAuthenticator) Authenticate(ctx context.Context, username, password string) (authuser.Info, string, error) {
func (p *passwordAuthenticator) Authenticate(_ context.Context, username, password string) (authuser.Info, string, error) {
// empty username or password are not allowed
if username == "" || password == "" {
return nil, "", IncorrectPasswordError
@@ -69,17 +76,27 @@ func (p *passwordAuthenticator) Authenticate(ctx context.Context, username, pass
return nil, providerOptions.Name, err
}
linkedAccount, err := p.userGetter.findMappedUser(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
}
}
}