Refactor authenticator

Signed-off-by: hongming <hongming@kubesphere.io>
This commit is contained in:
hongming
2021-08-17 11:34:51 +08:00
parent 83df7d1ffd
commit 4b5b1c64bc
41 changed files with 1923 additions and 758 deletions

View File

@@ -21,27 +21,18 @@ package auth
import (
"context"
"fmt"
"net/http"
"net/mail"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"golang.org/x/crypto/bcrypt"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
authuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
@@ -51,139 +42,20 @@ var (
AccountIsNotActiveError = fmt.Errorf("account is not active")
)
// PasswordAuthenticator is an interface implemented by authenticator which take a
// username and password.
type PasswordAuthenticator interface {
Authenticate(username, password string) (authuser.Info, string, error)
Authenticate(ctx context.Context, username, password string) (authuser.Info, string, error)
}
type OAuthAuthenticator interface {
Authenticate(provider, code string) (authuser.Info, string, error)
}
type passwordAuthenticator struct {
ksClient kubesphere.Interface
userGetter *userGetter
authOptions *authoptions.AuthenticationOptions
}
type oauth2Authenticator struct {
ksClient kubesphere.Interface
userGetter *userGetter
authOptions *authoptions.AuthenticationOptions
Authenticate(ctx context.Context, provider string, req *http.Request) (authuser.Info, string, error)
}
type userGetter struct {
userLister iamv1alpha2listers.UserLister
}
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
userLister iamv1alpha2listers.UserLister,
options *authoptions.AuthenticationOptions) PasswordAuthenticator {
passwordAuthenticator := &passwordAuthenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: userLister},
authOptions: options,
}
return passwordAuthenticator
}
func NewOAuthAuthenticator(ksClient kubesphere.Interface,
ksInformer informers.SharedInformerFactory,
options *authoptions.AuthenticationOptions) OAuthAuthenticator {
oauth2Authenticator := &oauth2Authenticator{
ksClient: ksClient,
userGetter: &userGetter{userLister: ksInformer.Iam().V1alpha2().Users().Lister()},
authOptions: options,
}
return oauth2Authenticator
}
func (p *passwordAuthenticator) Authenticate(username, password string) (authuser.Info, string, error) {
// empty username or password are not allowed
if username == "" || password == "" {
return nil, "", IncorrectPasswordError
}
// generic identity provider has higher priority
for _, providerOptions := range p.authOptions.OAuthOptions.IdentityProviders {
// the admin account in kubesphere has the highest priority
if username == constants.AdminUserName {
break
}
if genericProvider, _ := identityprovider.GetGenericProvider(providerOptions.Name); genericProvider != nil {
authenticated, err := genericProvider.Authenticate(username, password)
if err != nil {
if errors.IsUnauthorized(err) {
continue
}
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
}
}
}
// kubesphere account
user, err := p.userGetter.findUser(username)
if err != nil {
// ignore not found error
if !errors.IsNotFound(err) {
klog.Error(err)
return nil, "", err
}
}
// check user status
if user != nil && (user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive) {
if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
klog.Errorf("%s, username: %s", RateLimitExceededError, username)
return nil, "", RateLimitExceededError
} else {
// state not active
klog.Errorf("%s, username: %s", AccountIsNotActiveError, username)
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 != nil && user.Spec.EncryptedPassword != "" {
if err = PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
klog.Error(err)
return nil, "", err
}
u := &authuser.DefaultInfo{
Name: user.Name,
}
// check if the password is initialized
if uninitialized := user.Annotations[iamv1alpha2.UninitializedAnnotation]; uninitialized != "" {
u.Extra = map[string][]string{
iamv1alpha2.ExtraUninitialized: {uninitialized},
}
}
return u, "", nil
}
return nil, "", IncorrectPasswordError
}
func preRegistrationUser(idp string, identity identityprovider.Identity) authuser.Info {
return &authuser.DefaultInfo{
Name: iamv1alpha2.PreRegistrationUser,
@@ -193,95 +65,32 @@ func preRegistrationUser(idp string, identity identityprovider.Identity) authuse
iamv1alpha2.ExtraUsername: {identity.GetUsername()},
iamv1alpha2.ExtraEmail: {identity.GetEmail()},
},
Groups: []string{iamv1alpha2.PreRegistrationUserGroup},
}
}
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 {
klog.Error(err)
return nil, "", err
}
oauthIdentityProvider, err := identityprovider.GetOAuthProvider(providerOptions.Name)
if err != nil {
klog.Error(err)
return nil, "", err
}
authenticated, err := oauthIdentityProvider.IdentityExchange(code)
if err != nil {
klog.Error(err)
return nil, "", err
}
user, err := o.userGetter.findLinkedAccount(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 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
}
}
if user != nil {
return &authuser.DefaultInfo{Name: user.GetName()}, providerOptions.Name, nil
}
return nil, "", errors.NewNotFound(iamv1alpha2.Resource("user"), authenticated.GetUsername())
}
func PasswordVerify(encryptedPassword, password string) error {
if err := bcrypt.CompareHashAndPassword([]byte(encryptedPassword), []byte(password)); err != nil {
return IncorrectPasswordError
}
return nil
}
// findUser
// 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 {
return u.userLister.Get(username)
} else {
users, err := u.userLister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
for _, find := range users {
if find.Spec.Email == username {
return find, nil
}
}
users, err := u.userLister.List(labels.Everything())
if err != nil {
klog.Error(err)
return nil, err
}
for _, user := range users {
if user.Spec.Email == username {
return user, nil
}
}
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), username)
}
func (u *userGetter) findLinkedAccount(idp, uid string) (*iamv1alpha2.User, error) {
// findMappedUser returns the user which mapped to the identity
func (u *userGetter) findMappedUser(idp, uid string) (*iamv1alpha2.User, error) {
selector := labels.SelectorFromSet(labels.Set{
iamv1alpha2.IdentifyProviderLabel: idp,
iamv1alpha2.OriginUIDLabel: uid,

83
pkg/models/auth/oauth.go Normal file
View File

@@ -0,0 +1,83 @@
/*
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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"net/http"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"k8s.io/apimachinery/pkg/api/errors"
authuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
)
type oauthAuthenticator struct {
*userGetter
options *authentication.Options
}
func NewOAuthAuthenticator(userLister iamv1alpha2listers.UserLister,
options *authentication.Options) OAuthAuthenticator {
authenticator := &oauthAuthenticator{
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)
// identity provider not registered
if err != nil {
klog.Error(err)
return nil, "", err
}
identityProvider, err := identityprovider.GetOAuthProvider(options.Name)
if err != nil {
klog.Error(err)
return nil, "", err
}
identity, err := identityProvider.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 {
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
}
return nil, "", errors.NewNotFound(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularUser), identity.GetUsername())
}

View File

@@ -0,0 +1,203 @@
/*
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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"fmt"
"net/http"
"reflect"
"testing"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"github.com/mitchellh/mapstructure"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"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 Test_oauthAuthenticator_Authenticate(t *testing.T) {
oauthOptions := &authentication.Options{
OAuthOptions: &oauth.Options{
IdentityProviders: []oauth.IdentityProviderOptions{
{
Name: "fake",
MappingMethod: "auto",
Type: "FakeIdentityProvider",
Provider: oauth.DynamicOptions{
"identities": map[string]interface{}{
"code1": map[string]string{
"uid": "100001",
"email": "user1@kubesphere.io",
"username": "user1",
},
},
},
},
},
},
}
identityprovider.RegisterOAuthProvider(&fakeProviderFactory{})
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", "fake"))
if err != nil {
t.Fatal(err)
}
type args struct {
ctx context.Context
provider string
req *http.Request
}
tests := []struct {
name string
oauthAuthenticator OAuthAuthenticator
args args
userInfo user.Info
provider string
wantErr bool
}{
{
name: "Should successfully",
oauthAuthenticator: NewOAuthAuthenticator(
ksInformerFactory.Iam().V1alpha2().Users().Lister(),
oauthOptions,
),
args: args{
ctx: context.Background(),
provider: "fake",
req: must(http.NewRequest(http.MethodGet, "https://ks-console.kubesphere.io/oauth/callback/test?code=code1&state=100001", nil)),
},
userInfo: &user.DefaultInfo{
Name: "user1",
},
provider: "fake",
wantErr: false,
},
{
name: "Should successfully",
oauthAuthenticator: NewOAuthAuthenticator(
ksInformerFactory.Iam().V1alpha2().Users().Lister(),
oauthOptions,
),
args: args{
ctx: context.Background(),
provider: "fake1",
req: must(http.NewRequest(http.MethodGet, "https://ks-console.kubesphere.io/oauth/callback/test?code=code1&state=100001", nil)),
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userInfo, provider, err := tt.oauthAuthenticator.Authenticate(tt.args.ctx, tt.args.provider, tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("Authenticate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(userInfo, tt.userInfo) {
t.Errorf("Authenticate() got = %v, want %v", userInfo, tt.userInfo)
}
if provider != tt.provider {
t.Errorf("Authenticate() got = %v, want %v", provider, tt.provider)
}
})
}
}
func must(r *http.Request, err error) *http.Request {
if err != nil {
panic(err)
}
return r
}
func newUser(username string, uid string, idp string) *iamv1alpha2.User {
return &iamv1alpha2.User{
TypeMeta: metav1.TypeMeta{
Kind: iamv1alpha2.ResourceKindUser,
APIVersion: iamv1alpha2.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: username,
Labels: map[string]string{
iamv1alpha2.IdentifyProviderLabel: idp,
iamv1alpha2.OriginUIDLabel: uid,
},
},
}
}
type fakeProviderFactory struct {
}
type fakeProvider struct {
Identities map[string]fakeIdentity `json:"identities"`
}
type fakeIdentity struct {
UID string `json:"uid"`
Username string `json:"username"`
Email string `json:"email"`
}
func (f fakeIdentity) GetUserID() string {
return f.UID
}
func (f fakeIdentity) GetUsername() string {
return f.Username
}
func (f fakeIdentity) GetEmail() string {
return f.Email
}
func (fakeProviderFactory) Type() string {
return "FakeIdentityProvider"
}
func (fakeProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
var fakeProvider fakeProvider
if err := mapstructure.Decode(options, &fakeProvider); err != nil {
return nil, err
}
return &fakeProvider, nil
}
func (f fakeProvider) IdentityExchangeCallback(req *http.Request) (identityprovider.Identity, error) {
code := req.URL.Query().Get("code")
if identity, ok := f.Identities[code]; ok {
return identity, nil
}
return nil, fmt.Errorf("authorization failed")
}

134
pkg/models/auth/password.go Normal file
View File

@@ -0,0 +1,134 @@
/*
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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"golang.org/x/crypto/bcrypt"
"k8s.io/apimachinery/pkg/api/errors"
authuser "k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
)
type passwordAuthenticator struct {
userGetter *userGetter
authOptions *authentication.Options
}
func NewPasswordAuthenticator(userLister iamv1alpha2listers.UserLister,
options *authentication.Options) PasswordAuthenticator {
passwordAuthenticator := &passwordAuthenticator{
userGetter: &userGetter{userLister: userLister},
authOptions: options,
}
return passwordAuthenticator
}
func (p *passwordAuthenticator) Authenticate(ctx context.Context, username, password string) (authuser.Info, string, error) {
// empty username or password are not allowed
if username == "" || password == "" {
return nil, "", IncorrectPasswordError
}
// generic identity provider has higher priority
for _, providerOptions := range p.authOptions.OAuthOptions.IdentityProviders {
// the admin account in kubesphere has the highest priority
if username == constants.AdminUserName {
break
}
if genericProvider, _ := identityprovider.GetGenericProvider(providerOptions.Name); genericProvider != nil {
authenticated, err := genericProvider.Authenticate(username, password)
if err != nil {
if errors.IsUnauthorized(err) {
continue
}
return nil, providerOptions.Name, err
}
linkedAccount, err := p.userGetter.findMappedUser(providerOptions.Name, authenticated.GetUserID())
// using this method requires you to manually provision users.
if providerOptions.MappingMethod == oauth.MappingMethodLookup && linkedAccount == nil {
continue
}
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
}
}
}
// kubesphere account
user, err := p.userGetter.findUser(username)
if err != nil {
// ignore not found error
if !errors.IsNotFound(err) {
klog.Error(err)
return nil, "", err
}
}
// check user status
if user != nil && (user.Status.State == nil || *user.Status.State != iamv1alpha2.UserActive) {
if user.Status.State != nil && *user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
klog.Errorf("%s, username: %s", RateLimitExceededError, username)
return nil, "", RateLimitExceededError
} else {
// state not active
klog.Errorf("%s, username: %s", AccountIsNotActiveError, username)
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 != nil && user.Spec.EncryptedPassword != "" {
if err = PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
klog.Error(err)
return nil, "", err
}
u := &authuser.DefaultInfo{
Name: user.Name,
}
// check if the password is initialized
if uninitialized := user.Annotations[iamv1alpha2.UninitializedAnnotation]; uninitialized != "" {
u.Extra = map[string][]string{
iamv1alpha2.ExtraUninitialized: {uninitialized},
}
}
return u, "", nil
}
return nil, "", IncorrectPasswordError
}
func PasswordVerify(encryptedPassword, password string) error {
if err := bcrypt.CompareHashAndPassword([]byte(encryptedPassword), []byte(password)); err != nil {
return IncorrectPasswordError
}
return nil
}

View File

@@ -23,99 +23,87 @@ import (
"fmt"
"time"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
)
// TokenManagementInterface Cache issued token, support revocation of tokens after issuance
type TokenManagementInterface interface {
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
Verify(token string) (user.Info, error)
// IssueTo issues a token a User, return error if issuing process failed
IssueTo(user user.Info) (*oauth.Token, error)
// Verify the given token and returns token.VerifiedResponse
Verify(token string) (*token.VerifiedResponse, error)
// IssueTo issue a token for the specified user
IssueTo(request *token.IssueRequest) (string, error)
// Revoke revoke the specified token
Revoke(token string) error
// RevokeAllUserTokens revoke all user tokens
RevokeAllUserTokens(username string) error
}
type tokenOperator struct {
issuer token.Issuer
options *authoptions.AuthenticationOptions
options *authentication.Options
cache cache.Interface
}
func NewTokenOperator(cache cache.Interface, options *authoptions.AuthenticationOptions) TokenManagementInterface {
func (t tokenOperator) Revoke(token string) error {
pattern := fmt.Sprintf("kubesphere:user:*:token:%s", token)
if keys, err := t.cache.Keys(pattern); err != nil {
klog.Error(err)
return err
} else if len(keys) > 0 {
if err := t.cache.Del(keys...); err != nil {
klog.Error(err)
return err
}
}
return nil
}
func NewTokenOperator(cache cache.Interface, issuer token.Issuer, options *authentication.Options) TokenManagementInterface {
operator := &tokenOperator{
issuer: token.NewTokenIssuer(options.JwtSecret, options.MaximumClockSkew),
issuer: issuer,
options: options,
cache: cache,
}
return operator
}
func (t tokenOperator) Verify(tokenStr string) (user.Info, error) {
authenticated, tokenType, err := t.issuer.Verify(tokenStr)
func (t *tokenOperator) Verify(tokenStr string) (*token.VerifiedResponse, error) {
response, err := t.issuer.Verify(tokenStr)
if err != nil {
return nil, err
}
if t.options.OAuthOptions.AccessTokenMaxAge == 0 ||
tokenType == token.StaticToken {
return authenticated, nil
response.TokenType == token.StaticToken {
return response, nil
}
if err := t.tokenCacheValidate(authenticated.GetName(), tokenStr); err != nil {
if err := t.tokenCacheValidate(response.User.GetName(), tokenStr); err != nil {
return nil, err
}
return authenticated, nil
return response, nil
}
func (t tokenOperator) IssueTo(user user.Info) (*oauth.Token, error) {
accessTokenExpiresIn := t.options.OAuthOptions.AccessTokenMaxAge
refreshTokenExpiresIn := accessTokenExpiresIn + t.options.OAuthOptions.AccessTokenInactivityTimeout
accessToken, err := t.issuer.IssueTo(user, token.AccessToken, accessTokenExpiresIn)
func (t *tokenOperator) IssueTo(request *token.IssueRequest) (string, error) {
tokenStr, err := t.issuer.IssueTo(request)
if err != nil {
klog.Error(err)
return nil, err
return "", err
}
refreshToken, err := t.issuer.IssueTo(user, token.RefreshToken, refreshTokenExpiresIn)
if err != nil {
klog.Error(err)
return nil, err
}
result := &oauth.Token{
AccessToken: accessToken,
TokenType: "Bearer",
RefreshToken: refreshToken,
ExpiresIn: int(accessTokenExpiresIn.Seconds()),
}
if !t.options.MultipleLogin {
if err = t.RevokeAllUserTokens(user.GetName()); err != nil {
if request.ExpiresIn > 0 {
if err = t.cacheToken(request.User.GetName(), tokenStr, request.ExpiresIn); err != nil {
klog.Error(err)
return nil, err
return "", err
}
}
if accessTokenExpiresIn > 0 {
if err = t.cacheToken(user.GetName(), accessToken, accessTokenExpiresIn); err != nil {
klog.Error(err)
return nil, err
}
if err = t.cacheToken(user.GetName(), refreshToken, refreshTokenExpiresIn); err != nil {
klog.Error(err)
return nil, err
}
}
return result, nil
return tokenStr, nil
}
func (t tokenOperator) RevokeAllUserTokens(username string) error {
// RevokeAllUserTokens revoke all user tokens in the cache
func (t *tokenOperator) RevokeAllUserTokens(username string) error {
pattern := fmt.Sprintf("kubesphere:user:%s:token:*", username)
if keys, err := t.cache.Keys(pattern); err != nil {
klog.Error(err)
@@ -129,7 +117,8 @@ func (t tokenOperator) RevokeAllUserTokens(username string) error {
return nil
}
func (t tokenOperator) tokenCacheValidate(username, token string) error {
// tokenCacheValidate verify that the token is in the cache
func (t *tokenOperator) tokenCacheValidate(username, token string) error {
key := fmt.Sprintf("kubesphere:user:%s:token:%s", username, token)
if exist, err := t.cache.Exists(key); err != nil {
return err
@@ -141,7 +130,8 @@ func (t tokenOperator) tokenCacheValidate(username, token string) error {
return nil
}
func (t tokenOperator) cacheToken(username, token string, duration time.Duration) error {
// cacheToken cache the token for a period of time
func (t *tokenOperator) cacheToken(username, token string, duration time.Duration) error {
key := fmt.Sprintf("kubesphere:user:%s:token:%s", username, token)
if err := t.cache.Set(key, token, duration); err != nil {
klog.Error(err)

View File

@@ -19,13 +19,14 @@ import (
"context"
"fmt"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/api/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/api"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/models/auth"
@@ -43,7 +44,7 @@ type IdentityManagementInterface interface {
PasswordVerify(username string, password string) error
}
func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authentication.Options) IdentityManagementInterface {
im := &imOperator{
ksClient: ksClient,
userGetter: userGetter,
@@ -57,7 +58,7 @@ type imOperator struct {
ksClient kubesphere.Interface
userGetter resources.Interface
loginRecordGetter resources.Interface
options *authoptions.AuthenticationOptions
options *authentication.Options
}
// UpdateUser returns user information after update.