improve identity provider plugin
Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 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 im
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/mail"
|
||||
|
||||
"github.com/go-ldap/ldap"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
authuser "k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
|
||||
"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"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
AuthRateLimitExceeded = fmt.Errorf("auth rate limit exceeded")
|
||||
AuthFailedIncorrectPassword = fmt.Errorf("incorrect password")
|
||||
AuthFailedAccountIsNotActive = fmt.Errorf("account is not active")
|
||||
AuthFailedIdentityMappingNotMatch = fmt.Errorf("identity mapping not match")
|
||||
)
|
||||
|
||||
type PasswordAuthenticator interface {
|
||||
Authenticate(username, password string) (authuser.Info, error)
|
||||
}
|
||||
|
||||
type passwordAuthenticator struct {
|
||||
ksClient kubesphere.Interface
|
||||
userLister iamv1alpha2listers.UserLister
|
||||
options *authoptions.AuthenticationOptions
|
||||
}
|
||||
|
||||
func NewPasswordAuthenticator(ksClient kubesphere.Interface,
|
||||
userLister iamv1alpha2listers.UserLister,
|
||||
options *authoptions.AuthenticationOptions) PasswordAuthenticator {
|
||||
return &passwordAuthenticator{
|
||||
ksClient: ksClient,
|
||||
userLister: userLister,
|
||||
options: options}
|
||||
}
|
||||
|
||||
func (im *passwordAuthenticator) Authenticate(username, password string) (authuser.Info, error) {
|
||||
|
||||
user, err := im.searchUser(username)
|
||||
if err != nil {
|
||||
// internal error
|
||||
if !errors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
providerOptions, ldapProvider := im.getLdapProvider()
|
||||
|
||||
// no identity provider
|
||||
// even auth failed, still return username to record login attempt
|
||||
if user == nil && (providerOptions == nil || providerOptions.MappingMethod != oauth.MappingMethodAuto) {
|
||||
return nil, AuthFailedIncorrectPassword
|
||||
}
|
||||
|
||||
if user != nil && user.Status.State != iamv1alpha2.UserActive {
|
||||
if user.Status.State == iamv1alpha2.UserAuthLimitExceeded {
|
||||
klog.Errorf("%s, username: %s", AuthRateLimitExceeded, username)
|
||||
return nil, AuthRateLimitExceeded
|
||||
} else {
|
||||
klog.Errorf("%s, username: %s", AuthFailedAccountIsNotActive, username)
|
||||
return nil, AuthFailedAccountIsNotActive
|
||||
}
|
||||
}
|
||||
|
||||
// able to login using the locally principal admin account and password in case of a disruption of LDAP services.
|
||||
if ldapProvider != nil && username != constants.AdminUserName {
|
||||
if providerOptions.MappingMethod == oauth.MappingMethodLookup &&
|
||||
(user == nil || user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name) {
|
||||
klog.Error(AuthFailedIdentityMappingNotMatch)
|
||||
return nil, AuthFailedIdentityMappingNotMatch
|
||||
}
|
||||
if providerOptions.MappingMethod == oauth.MappingMethodAuto &&
|
||||
user != nil && user.Labels[iamv1alpha2.IdentifyProviderLabel] != providerOptions.Name {
|
||||
klog.Error(AuthFailedIdentityMappingNotMatch)
|
||||
return nil, AuthFailedIdentityMappingNotMatch
|
||||
}
|
||||
|
||||
authenticated, err := ldapProvider.Authenticate(username, password)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) || ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||
return nil, AuthFailedIncorrectPassword
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if authenticated != nil && user == nil {
|
||||
authenticated.Labels = map[string]string{iamv1alpha2.IdentifyProviderLabel: providerOptions.Name}
|
||||
if authenticated, err = im.ksClient.IamV1alpha2().Users().Create(authenticated); err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if authenticated != nil {
|
||||
return &authuser.DefaultInfo{
|
||||
Name: authenticated.Name,
|
||||
UID: string(authenticated.UID),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
|
||||
return &authuser.DefaultInfo{
|
||||
Name: user.Name,
|
||||
UID: string(user.UID),
|
||||
Groups: user.Spec.Groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, AuthFailedIncorrectPassword
|
||||
}
|
||||
|
||||
func (im *passwordAuthenticator) searchUser(username string) (*iamv1alpha2.User, error) {
|
||||
|
||||
if _, err := mail.ParseAddress(username); err != nil {
|
||||
return im.userLister.Get(username)
|
||||
} else {
|
||||
users, err := im.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFound(iamv1alpha2.Resource("user"), username)
|
||||
}
|
||||
|
||||
func (im *passwordAuthenticator) getLdapProvider() (*oauth.IdentityProviderOptions, identityprovider.LdapProvider) {
|
||||
for _, options := range im.options.OAuthOptions.IdentityProviders {
|
||||
if options.Type == identityprovider.LdapIdentityProvider {
|
||||
if provider, err := identityprovider.NewLdapProvider(options.Provider); err != nil {
|
||||
klog.Error(err)
|
||||
} else {
|
||||
return &options, provider
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
package im
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
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/informers"
|
||||
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
|
||||
"kubesphere.io/kubesphere/pkg/models/auth"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
)
|
||||
|
||||
type IdentityManagementInterface interface {
|
||||
@@ -35,41 +35,40 @@ type IdentityManagementInterface interface {
|
||||
UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
|
||||
DescribeUser(username string) (*iamv1alpha2.User, error)
|
||||
ModifyPassword(username string, password string) error
|
||||
ListLoginRecords(query *query.Query) (*api.ListResult, error)
|
||||
ListLoginRecords(username string, query *query.Query) (*api.ListResult, error)
|
||||
PasswordVerify(username string, password string) error
|
||||
}
|
||||
|
||||
func NewOperator(ksClient kubesphere.Interface, factory informers.InformerFactory, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
|
||||
im := &defaultIMOperator{
|
||||
ksClient: ksClient,
|
||||
resourceGetter: resourcev1alpha3.NewResourceGetter(factory),
|
||||
options: options,
|
||||
func NewOperator(ksClient kubesphere.Interface, userGetter resources.Interface, loginRecordGetter resources.Interface, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
|
||||
im := &imOperator{
|
||||
ksClient: ksClient,
|
||||
userGetter: userGetter,
|
||||
loginRecordGetter: loginRecordGetter,
|
||||
options: options,
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
type defaultIMOperator struct {
|
||||
ksClient kubesphere.Interface
|
||||
resourceGetter *resourcev1alpha3.ResourceGetter
|
||||
options *authoptions.AuthenticationOptions
|
||||
type imOperator struct {
|
||||
ksClient kubesphere.Interface
|
||||
userGetter resources.Interface
|
||||
loginRecordGetter resources.Interface
|
||||
options *authoptions.AuthenticationOptions
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
|
||||
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", user.Name)
|
||||
// UpdateUser returns user information after update.
|
||||
func (im *imOperator) UpdateUser(new *iamv1alpha2.User) (*iamv1alpha2.User, error) {
|
||||
old, err := im.fetch(new.Name)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
old := obj.(*iamv1alpha2.User).DeepCopy()
|
||||
if user.Annotations == nil {
|
||||
user.Annotations = make(map[string]string, 0)
|
||||
if old.Annotations == nil {
|
||||
old.Annotations = make(map[string]string, 0)
|
||||
}
|
||||
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation]
|
||||
user.Spec.EncryptedPassword = old.Spec.EncryptedPassword
|
||||
user.Status = old.Status
|
||||
|
||||
updated, err := im.ksClient.IamV1alpha2().Users().Update(user)
|
||||
// keep encrypted password
|
||||
new.Spec.EncryptedPassword = old.Spec.EncryptedPassword
|
||||
updated, err := im.ksClient.IamV1alpha2().Users().Update(old)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
@@ -77,18 +76,23 @@ func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
|
||||
return ensurePasswordNotOutput(updated), nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) ModifyPassword(username string, password string) error {
|
||||
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
|
||||
func (im *imOperator) fetch(username string) (*iamv1alpha2.User, error) {
|
||||
obj, err := im.userGetter.Get("", username)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
user := obj.(*iamv1alpha2.User).DeepCopy()
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (im *imOperator) ModifyPassword(username string, password string) error {
|
||||
user, err := im.fetch(username)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
user := obj.(*iamv1alpha2.User).DeepCopy()
|
||||
delete(user.Annotations, iamv1alpha2.PasswordEncryptedAnnotation)
|
||||
user.Spec.EncryptedPassword = password
|
||||
|
||||
_, err = im.ksClient.IamV1alpha2().Users().Update(user)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
@@ -97,13 +101,12 @@ func (im *defaultIMOperator) ModifyPassword(username string, password string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
|
||||
result, err = im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", query)
|
||||
func (im *imOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
|
||||
result, err = im.userGetter.List("", query)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
for _, item := range result.Items {
|
||||
user := item.(*iamv1alpha2.User)
|
||||
@@ -114,40 +117,34 @@ func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResu
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) PasswordVerify(username string, password string) error {
|
||||
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
|
||||
func (im *imOperator) PasswordVerify(username string, password string) error {
|
||||
obj, err := im.userGetter.Get("", username)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
user := obj.(*iamv1alpha2.User)
|
||||
if checkPasswordHash(password, user.Spec.EncryptedPassword) {
|
||||
return nil
|
||||
if err = auth.PasswordVerify(user.Spec.EncryptedPassword, password); err != nil {
|
||||
return err
|
||||
}
|
||||
return AuthFailedIncorrectPassword
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPasswordHash(password, hash string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
|
||||
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
|
||||
func (im *imOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
|
||||
obj, err := im.userGetter.Get("", username)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := obj.(*iamv1alpha2.User)
|
||||
return ensurePasswordNotOutput(user), nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) DeleteUser(username string) error {
|
||||
func (im *imOperator) DeleteUser(username string) error {
|
||||
return im.ksClient.IamV1alpha2().Users().Delete(username, metav1.NewDeleteOptions(0))
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
|
||||
func (im *imOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
|
||||
user, err := im.ksClient.IamV1alpha2().Users().Create(user)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
@@ -156,8 +153,9 @@ func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (im *defaultIMOperator) ListLoginRecords(query *query.Query) (*api.ListResult, error) {
|
||||
result, err := im.resourceGetter.List(iamv1alpha2.ResourcesPluralLoginRecord, "", query)
|
||||
func (im *imOperator) ListLoginRecords(username string, q *query.Query) (*api.ListResult, error) {
|
||||
q.Filters[query.FieldLabel] = query.Value(fmt.Sprintf("%s=%s", iamv1alpha2.UserReferenceLabel, username))
|
||||
result, err := im.loginRecordGetter.List("", q)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
|
||||
@@ -15,25 +15,3 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
package im
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncryptPassword(t *testing.T) {
|
||||
password := "P@88w0rd"
|
||||
encryptedPassword, err := hashPassword(password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !checkPasswordHash(password, encryptedPassword) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(encryptedPassword)
|
||||
}
|
||||
|
||||
func hashPassword(password string) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 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 im
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
"kubesphere.io/kubesphere/pkg/utils/net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LoginRecorder interface {
|
||||
RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error
|
||||
}
|
||||
|
||||
type loginRecorder struct {
|
||||
ksClient kubesphere.Interface
|
||||
}
|
||||
|
||||
func NewLoginRecorder(ksClient kubesphere.Interface) LoginRecorder {
|
||||
return &loginRecorder{
|
||||
ksClient: ksClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loginRecorder) RecordLogin(username string, loginType iamv1alpha2.LoginType, provider string, authErr error, req *http.Request) error {
|
||||
// This is a temporary solution in case of user login with email,
|
||||
// '@' is not allowed in Kubernetes object name.
|
||||
username = strings.Replace(username, "@", "-", -1)
|
||||
|
||||
loginEntry := &iamv1alpha2.LoginRecord{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: fmt.Sprintf("%s-", username),
|
||||
Labels: map[string]string{
|
||||
iamv1alpha2.UserReferenceLabel: username,
|
||||
},
|
||||
},
|
||||
Spec: iamv1alpha2.LoginRecordSpec{
|
||||
Type: loginType,
|
||||
Provider: provider,
|
||||
Success: true,
|
||||
Reason: iamv1alpha2.AuthenticatedSuccessfully,
|
||||
SourceIP: net.GetRequestIP(req),
|
||||
UserAgent: req.UserAgent(),
|
||||
},
|
||||
}
|
||||
|
||||
if authErr != nil {
|
||||
loginEntry.Spec.Success = false
|
||||
loginEntry.Spec.Reason = authErr.Error()
|
||||
}
|
||||
|
||||
_, err := l.ksClient.IamV1alpha2().LoginRecords().Create(loginEntry)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 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 im
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"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"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type tokenOperator struct {
|
||||
issuer token.Issuer
|
||||
options *authoptions.AuthenticationOptions
|
||||
cache cache.Interface
|
||||
}
|
||||
|
||||
func NewTokenOperator(cache cache.Interface, options *authoptions.AuthenticationOptions) TokenManagementInterface {
|
||||
operator := &tokenOperator{
|
||||
issuer: token.NewTokenIssuer(options.JwtSecret, options.MaximumClockSkew),
|
||||
options: options,
|
||||
cache: cache,
|
||||
}
|
||||
return operator
|
||||
}
|
||||
|
||||
func (t tokenOperator) Verify(tokenStr string) (user.Info, error) {
|
||||
authenticated, tokenType, err := t.issuer.Verify(tokenStr)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if t.options.OAuthOptions.AccessTokenMaxAge == 0 ||
|
||||
tokenType == token.StaticToken {
|
||||
return authenticated, nil
|
||||
}
|
||||
if err := t.tokenCacheValidate(authenticated.GetName(), tokenStr); err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return authenticated, 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)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, 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 {
|
||||
klog.Error(err)
|
||||
return nil, 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
|
||||
}
|
||||
|
||||
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)
|
||||
return err
|
||||
} else if len(keys) > 0 {
|
||||
if err := t.cache.Del(keys...); err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
} else if !exist {
|
||||
return fmt.Errorf("token not found in cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user