@@ -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(
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user