From aa05c2baf438b147b81bb26092e87c37120a2424 Mon Sep 17 00:00:00 2001 From: hongming Date: Sun, 22 Mar 2020 23:17:43 +0800 Subject: [PATCH] update Signed-off-by: hongming --- pkg/api/auth/token/issuer.go | 10 -- pkg/api/auth/token/jwt.go | 77 --------- pkg/api/auth/token/jwt_test.go | 52 ------ pkg/api/auth/token/user.go | 12 -- pkg/apiserver/apiserver.go | 17 +- .../authenticators/basic/basic.go | 58 +++++++ .../authenticators/jwttoken/jwt_token.go | 24 +-- pkg/apiserver/authentication/oauth/oauth.go | 30 ++++ .../authentication/oauth/simple_config.go | 37 +++++ .../authentication/request/anonymous.go | 24 --- .../request/anonymous/anonymous.go | 46 ++++++ .../request/basictoken/basic_token.go | 56 +++++++ pkg/apiserver/authentication/token/issuer.go | 31 ++++ pkg/apiserver/authentication/token/jwt.go | 124 +++++++++++++++ .../authentication/token/jwt_test.go | 72 +++++++++ pkg/apiserver/authentication/token/token.go | 1 - pkg/apiserver/authentication/token/user.go | 27 ++++ .../authorizerfactory/opa_test.go | 3 +- pkg/kapis/iam/v1alpha2/handler.go | 3 +- pkg/kapis/iam/v1alpha2/register.go | 12 +- pkg/kapis/oauth/handler.go | 56 ++++++- pkg/kapis/oauth/register.go | 22 +-- pkg/models/iam/am/fake_operator.go | 14 +- pkg/models/iam/im/fake_operator.go | 25 +++ pkg/models/iam/im/im.go | 148 ++---------------- pkg/simple/client/ldap/interface.go | 4 +- pkg/simple/client/ldap/ldap.go | 2 +- pkg/simple/client/ldap/simple_ldap.go | 2 +- pkg/simple/client/ldap/simple_ldap_test.go | 4 +- 29 files changed, 626 insertions(+), 367 deletions(-) delete mode 100644 pkg/api/auth/token/issuer.go delete mode 100644 pkg/api/auth/token/jwt.go delete mode 100644 pkg/api/auth/token/jwt_test.go delete mode 100644 pkg/api/auth/token/user.go create mode 100644 pkg/apiserver/authentication/authenticators/basic/basic.go create mode 100644 pkg/apiserver/authentication/oauth/oauth.go create mode 100644 pkg/apiserver/authentication/oauth/simple_config.go delete mode 100644 pkg/apiserver/authentication/request/anonymous.go create mode 100644 pkg/apiserver/authentication/request/anonymous/anonymous.go create mode 100644 pkg/apiserver/authentication/request/basictoken/basic_token.go create mode 100644 pkg/apiserver/authentication/token/issuer.go create mode 100644 pkg/apiserver/authentication/token/jwt.go create mode 100644 pkg/apiserver/authentication/token/jwt_test.go delete mode 100644 pkg/apiserver/authentication/token/token.go create mode 100644 pkg/apiserver/authentication/token/user.go create mode 100644 pkg/models/iam/im/fake_operator.go diff --git a/pkg/api/auth/token/issuer.go b/pkg/api/auth/token/issuer.go deleted file mode 100644 index 199ce26e1..000000000 --- a/pkg/api/auth/token/issuer.go +++ /dev/null @@ -1,10 +0,0 @@ -package token - -// Issuer issues token to user, tokens are required to perform mutating requests to resources -type Issuer interface { - // IssueTo issues a token a User, return error if issuing process failed - IssueTo(User) (string, error) - - // Verify verifies a token, and return a User if it's a valid token, otherwise return error - Verify(string) (User, error) -} diff --git a/pkg/api/auth/token/jwt.go b/pkg/api/auth/token/jwt.go deleted file mode 100644 index 88cec5685..000000000 --- a/pkg/api/auth/token/jwt.go +++ /dev/null @@ -1,77 +0,0 @@ -package token - -import ( - "fmt" - "github.com/dgrijalva/jwt-go" - "kubesphere.io/kubesphere/pkg/api/iam" - "kubesphere.io/kubesphere/pkg/server/errors" - "time" -) - -const DefaultIssuerName = "kubesphere" - -var errInvalidToken = errors.New("invalid token") - -type claims struct { - Username string `json:"username"` - UID string `json:"uid"` - Email string `json:"email"` - // Currently, we are not using any field in jwt.StandardClaims - jwt.StandardClaims -} - -type jwtTokenIssuer struct { - name string - secret []byte - keyFunc jwt.Keyfunc -} - -func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) { - if len(tokenString) == 0 { - return nil, errInvalidToken - } - - clm := &claims{} - - _, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc) - if err != nil { - return nil, err - } - - return &iam.User{Name: clm.Username, UID: clm.UID, Email: clm.Email}, nil -} - -func (s *jwtTokenIssuer) IssueTo(user User) (string, error) { - clm := &claims{ - Username: user.GetName(), - UID: user.GetUID(), - Email: user.GetEmail(), - StandardClaims: jwt.StandardClaims{ - IssuedAt: time.Now().Unix(), - Issuer: s.name, - NotBefore: time.Now().Unix(), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm) - tokenString, err := token.SignedString(s.secret) - if err != nil { - return "", err - } - - return tokenString, nil -} - -func NewJwtTokenIssuer(issuerName string, secret []byte) Issuer { - return &jwtTokenIssuer{ - name: issuerName, - secret: secret, - keyFunc: func(token *jwt.Token) (i interface{}, err error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { - return secret, nil - } else { - return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"]) - } - }, - } -} diff --git a/pkg/api/auth/token/jwt_test.go b/pkg/api/auth/token/jwt_test.go deleted file mode 100644 index 193b1f853..000000000 --- a/pkg/api/auth/token/jwt_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package token - -import ( - "github.com/google/go-cmp/cmp" - "kubesphere.io/kubesphere/pkg/api/iam" - "testing" -) - -func TestJwtTokenIssuer(t *testing.T) { - issuer := NewJwtTokenIssuer(DefaultIssuerName, []byte("kubesphere")) - - testCases := []struct { - description string - name string - uid string - email string - }{ - { - name: "admin", - uid: "b8be6edd-2c92-4535-9b2a-df6326474458", - email: "admin@kubesphere.io", - }, - { - name: "bar", - uid: "b8be6edd-2c92-4535-9b2a-df6326474452", - email: "bar@kubesphere.io", - }, - } - - for _, testCase := range testCases { - user := &iam.User{ - Name: testCase.name, - UID: testCase.uid, - } - - t.Run(testCase.description, func(t *testing.T) { - token, err := issuer.IssueTo(user) - if err != nil { - t.Fatal(err) - } - - got, err := issuer.Verify(token) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(user, got); len(diff) != 0 { - t.Errorf("%T differ (-got, +expected), %s", user, diff) - } - }) - } -} diff --git a/pkg/api/auth/token/user.go b/pkg/api/auth/token/user.go deleted file mode 100644 index 2a86e2f5f..000000000 --- a/pkg/api/auth/token/user.go +++ /dev/null @@ -1,12 +0,0 @@ -package token - -type User interface { - // Name - GetName() string - - // UID - GetUID() string - - // Email - GetEmail() string -} diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index fd071c821..7f83560d5 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -13,8 +13,12 @@ import ( "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api/auth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/basic" "kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken" - authenticationrequest "kubesphere.io/kubesphere/pkg/apiserver/authentication/request" + oauth2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory" "kubesphere.io/kubesphere/pkg/apiserver/authorization/path" unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union" @@ -35,6 +39,7 @@ import ( tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2" terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2" "kubesphere.io/kubesphere/pkg/models/iam/am" + "kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/devops" "kubesphere.io/kubesphere/pkg/simple/client/k8s" @@ -142,7 +147,7 @@ func (s *APIServer) installKubeSphereAPIs() { urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database())) urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config())) urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.AuthenticateOptions)) - urlruntime.Must(oauth.AddToContainer(s.container, s.AuthenticateOptions)) + urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient), &oauth2.SimpleConfigManager{})) urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container)) } @@ -184,10 +189,14 @@ func (s *APIServer) buildHandlerChain() { excludedPaths := []string{"/oauth/authorize", "/oauth/token"} pathAuthorizer, _ := path.NewAuthorizer(excludedPaths) - authorizer := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator(cache.NewSimpleCache()))) + authorizer := unionauthorizer.New(pathAuthorizer, + authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator())) handler = filters.WithAuthorization(handler, authorizer) - authn := unionauth.New(&authenticationrequest.AnonymousAuthenticator{}, bearertoken.New(jwttoken.NewTokenAuthenticator(s.CacheClient, s.AuthenticateOptions.JwtSecret))) + authn := unionauth.New(anonymous.NewAuthenticator(), + basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())), + bearertoken.New(jwttoken.NewTokenAuthenticator( + token.NewJwtTokenIssuer(token.DefaultIssuerName, s.AuthenticateOptions, s.CacheClient)))) handler = filters.WithAuthentication(handler, authn) handler = filters.WithRequestInfo(handler, requestInfoResolver) s.Server.Handler = handler diff --git a/pkg/apiserver/authentication/authenticators/basic/basic.go b/pkg/apiserver/authentication/authenticators/basic/basic.go new file mode 100644 index 000000000..b97073a7e --- /dev/null +++ b/pkg/apiserver/authentication/authenticators/basic/basic.go @@ -0,0 +1,58 @@ +/* + * + * 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 basic + +import ( + "context" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" + "kubesphere.io/kubesphere/pkg/models/iam/im" +) + +// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic. +// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token +// was given, authenticator will still give passed response at the condition user will be user.Anonymous +// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain, +// because some resources are public accessible. +type basicAuthenticator struct { + im im.IdentityManagementInterface +} + +func NewBasicAuthenticator(im im.IdentityManagementInterface) authenticator.Password { + return &basicAuthenticator{ + im: im, + } +} + +func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) { + + providedUser, err := t.im.Authenticate(username, password) + + if err != nil { + return nil, false, err + } + + return &authenticator.Response{ + User: &user.DefaultInfo{ + Name: providedUser.GetName(), + UID: providedUser.GetUID(), + Groups: []string{user.AllAuthenticated}, + }, + }, true, nil +} diff --git a/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go b/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go index 52a886486..bdbe8bd49 100644 --- a/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go +++ b/pkg/apiserver/authentication/authenticators/jwttoken/jwt_token.go @@ -4,44 +4,30 @@ import ( "context" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/user" - "kubesphere.io/kubesphere/pkg/api/auth/token" - "kubesphere.io/kubesphere/pkg/server/errors" - "kubesphere.io/kubesphere/pkg/simple/client/cache" + token2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" ) -var errTokenExpired = errors.New("expired token") - // TokenAuthenticator implements kubernetes token authenticate interface with our custom logic. // TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token // was given, authenticator will still give passed response at the condition user will be user.Anonymous // and group from user.AllUnauthenticated. This helps requests be passed along the handler chain, // because some resources are public accessible. type tokenAuthenticator struct { - cacheClient cache.Interface - jwtTokenIssuer token.Issuer + jwtTokenIssuer token2.Issuer } -func NewTokenAuthenticator(cacheClient cache.Interface, jwtSecret string) authenticator.Token { +func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token { return &tokenAuthenticator{ - cacheClient: cacheClient, - jwtTokenIssuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(jwtSecret)), + jwtTokenIssuer: issuer, } } func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) { - providedUser, err := t.jwtTokenIssuer.Verify(token) + providedUser, _, err := t.jwtTokenIssuer.Verify(token) if err != nil { return nil, false, err } - // TODO implement token cache - //_, err = t.cacheClient.Get(tokenKeyForUsername(providedUser.Name(), token)) - //if err != nil { - // return nil, false, errTokenExpired - //} - - // Should we need to refresh token? - return &authenticator.Response{ User: &user.DefaultInfo{ Name: providedUser.GetName(), diff --git a/pkg/apiserver/authentication/oauth/oauth.go b/pkg/apiserver/authentication/oauth/oauth.go new file mode 100644 index 000000000..12a9a3973 --- /dev/null +++ b/pkg/apiserver/authentication/oauth/oauth.go @@ -0,0 +1,30 @@ +/* + * + * 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 oauth + +import ( + "errors" + "golang.org/x/oauth2" +) + +var ConfigNotFound = errors.New("config not found") + +type Configuration interface { + Load(clientId string) (*oauth2.Config, error) +} diff --git a/pkg/apiserver/authentication/oauth/simple_config.go b/pkg/apiserver/authentication/oauth/simple_config.go new file mode 100644 index 000000000..7e0597561 --- /dev/null +++ b/pkg/apiserver/authentication/oauth/simple_config.go @@ -0,0 +1,37 @@ +/* + * + * 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 oauth + +import "golang.org/x/oauth2" + +type SimpleConfigManager struct { +} + +func (s *SimpleConfigManager) Load(clientId string) (*oauth2.Config, error) { + if clientId == "kubesphere-console-client" { + return &oauth2.Config{ + ClientID: "8b21fef43889a28f2bd6", + ClientSecret: "xb21fef43889a28f2bd6", + Endpoint: oauth2.Endpoint{AuthURL: "http://ks-apiserver.kubesphere-system.svc/oauth/authorize", TokenURL: "http://ks-apiserver.kubesphere.io/oauth/token"}, + RedirectURL: "http://ks-console.kubesphere-system.svc/oauth/token/implicit", + Scopes: nil, + }, nil + } + return nil, ConfigNotFound +} diff --git a/pkg/apiserver/authentication/request/anonymous.go b/pkg/apiserver/authentication/request/anonymous.go deleted file mode 100644 index f309e4564..000000000 --- a/pkg/apiserver/authentication/request/anonymous.go +++ /dev/null @@ -1,24 +0,0 @@ -package request - -import ( - "k8s.io/apiserver/pkg/authentication/authenticator" - "k8s.io/apiserver/pkg/authentication/user" - "net/http" - "strings" -) - -type AnonymousAuthenticator struct{} - -func (a *AnonymousAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { - auth := strings.TrimSpace(req.Header.Get("Authorization")) - if auth == "" { - return &authenticator.Response{ - User: &user.DefaultInfo{ - Name: user.Anonymous, - UID: "", - Groups: []string{user.AllUnauthenticated}, - }, - }, true, nil - } - return nil, false, nil -} diff --git a/pkg/apiserver/authentication/request/anonymous/anonymous.go b/pkg/apiserver/authentication/request/anonymous/anonymous.go new file mode 100644 index 000000000..9ff1115a2 --- /dev/null +++ b/pkg/apiserver/authentication/request/anonymous/anonymous.go @@ -0,0 +1,46 @@ +/* + * + * 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 anonymous + +import ( + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" + "net/http" + "strings" +) + +type Authenticator struct{} + +func NewAuthenticator() authenticator.Request { + return &Authenticator{} +} + +func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { + auth := strings.TrimSpace(req.Header.Get("Authorization")) + if auth == "" { + return &authenticator.Response{ + User: &user.DefaultInfo{ + Name: user.Anonymous, + UID: "", + Groups: []string{user.AllUnauthenticated}, + }, + }, true, nil + } + return nil, false, nil +} diff --git a/pkg/apiserver/authentication/request/basictoken/basic_token.go b/pkg/apiserver/authentication/request/basictoken/basic_token.go new file mode 100644 index 000000000..ebb75f6a5 --- /dev/null +++ b/pkg/apiserver/authentication/request/basictoken/basic_token.go @@ -0,0 +1,56 @@ +/* +Copyright 2014 The Kubernetes 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 basictoken + +import ( + "errors" + "k8s.io/apiserver/pkg/authentication/authenticator" + "net/http" +) + +type Authenticator struct { + auth authenticator.Password +} + +func New(auth authenticator.Password) *Authenticator { + return &Authenticator{auth} +} + +var invalidToken = errors.New("invalid basic token") + +func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) { + + username, password, ok := req.BasicAuth() + + if !ok { + return nil, false, nil + } + + resp, ok, err := a.auth.AuthenticatePassword(req.Context(), username, password) + // if we authenticated successfully, go ahead and remove the bearer token so that no one + // is ever tempted to use it inside of the API server + if ok { + req.Header.Del("Authorization") + } + + // If the token authenticator didn't error, provide a default error + if !ok && err == nil { + err = invalidToken + } + + return resp, ok, err +} diff --git a/pkg/apiserver/authentication/token/issuer.go b/pkg/apiserver/authentication/token/issuer.go new file mode 100644 index 000000000..5c1fdfa18 --- /dev/null +++ b/pkg/apiserver/authentication/token/issuer.go @@ -0,0 +1,31 @@ +/* + * + * 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 token + +// Issuer issues token to user, tokens are required to perform mutating requests to resources +type Issuer interface { + // IssueTo issues a token a User, return error if issuing process failed + IssueTo(User) (string, *Claims, error) + + // Verify verifies a token, and return a User if it's a valid token, otherwise return error + Verify(string) (User, *Claims, error) + + // Revoke a token, + Revoke(token string) error +} diff --git a/pkg/apiserver/authentication/token/jwt.go b/pkg/apiserver/authentication/token/jwt.go new file mode 100644 index 000000000..10e8db30e --- /dev/null +++ b/pkg/apiserver/authentication/token/jwt.go @@ -0,0 +1,124 @@ +/* + * + * 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 token + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "kubesphere.io/kubesphere/pkg/api/auth" + "kubesphere.io/kubesphere/pkg/api/iam" + "kubesphere.io/kubesphere/pkg/server/errors" + "kubesphere.io/kubesphere/pkg/simple/client/cache" + "time" +) + +const DefaultIssuerName = "kubesphere" + +var ( + errInvalidToken = errors.New("invalid token") + errTokenExpired = errors.New("expired token") +) + +type Claims struct { + Username string `json:"username"` + UID string `json:"uid"` + // Currently, we are not using any field in jwt.StandardClaims + jwt.StandardClaims +} + +type jwtTokenIssuer struct { + name string + options *auth.AuthenticationOptions + cache cache.Interface + keyFunc jwt.Keyfunc +} + +func (s *jwtTokenIssuer) Verify(tokenString string) (User, *Claims, error) { + if len(tokenString) == 0 { + return nil, nil, errInvalidToken + } + _, err := s.cache.Get(tokenCacheKey(tokenString)) + + if err != nil { + if err == cache.ErrNoSuchKey { + return nil, nil, errTokenExpired + } + return nil, nil, err + } + + clm := &Claims{} + + _, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc) + if err != nil { + return nil, nil, err + } + + return &iam.User{Name: clm.Username, UID: clm.UID}, clm, nil +} + +func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) { + clm := &Claims{ + Username: user.GetName(), + UID: user.GetUID(), + StandardClaims: jwt.StandardClaims{ + IssuedAt: time.Now().Unix(), + Issuer: s.name, + NotBefore: time.Now().Unix(), + }, + } + + if s.options.TokenExpiration > 0 { + clm.ExpiresAt = clm.IssuedAt + int64(s.options.TokenExpiration.Seconds()) + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm) + + tokenString, err := token.SignedString([]byte(s.options.JwtSecret)) + + if err != nil { + return "", nil, err + } + + s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.TokenExpiration) + + return tokenString, clm, nil +} + +func (s *jwtTokenIssuer) Revoke(token string) error { + return s.cache.Del(tokenCacheKey(token)) +} + +func NewJwtTokenIssuer(issuerName string, options *auth.AuthenticationOptions, cache cache.Interface) Issuer { + return &jwtTokenIssuer{ + name: issuerName, + options: options, + cache: cache, + keyFunc: func(token *jwt.Token) (i interface{}, err error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { + return []byte(options.JwtSecret), nil + } else { + return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"]) + } + }, + } +} + +func tokenCacheKey(token string) string { + return fmt.Sprintf("kubesphere:tokens:%s", token) +} diff --git a/pkg/apiserver/authentication/token/jwt_test.go b/pkg/apiserver/authentication/token/jwt_test.go new file mode 100644 index 000000000..bc7c0b380 --- /dev/null +++ b/pkg/apiserver/authentication/token/jwt_test.go @@ -0,0 +1,72 @@ +/* + * + * 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 token + +import ( + "github.com/google/go-cmp/cmp" + "kubesphere.io/kubesphere/pkg/api/auth" + "kubesphere.io/kubesphere/pkg/api/iam" + "kubesphere.io/kubesphere/pkg/simple/client/cache" + "testing" +) + +func TestJwtTokenIssuer(t *testing.T) { + options := auth.NewAuthenticateOptions() + options.JwtSecret = "kubesphere" + issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache()) + + testCases := []struct { + description string + name string + uid string + email string + }{ + { + name: "admin", + uid: "b8be6edd-2c92-4535-9b2a-df6326474458", + }, + { + name: "bar", + uid: "b8be6edd-2c92-4535-9b2a-df6326474452", + }, + } + + for _, testCase := range testCases { + user := &iam.User{ + Name: testCase.name, + UID: testCase.uid, + } + + t.Run(testCase.description, func(t *testing.T) { + token, _, err := issuer.IssueTo(user) + if err != nil { + t.Fatal(err) + } + + got, _, err := issuer.Verify(token) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(user, got); len(diff) != 0 { + t.Errorf("%T differ (-got, +expected), %s", user, diff) + } + }) + } +} diff --git a/pkg/apiserver/authentication/token/token.go b/pkg/apiserver/authentication/token/token.go deleted file mode 100644 index 1765cc067..000000000 --- a/pkg/apiserver/authentication/token/token.go +++ /dev/null @@ -1 +0,0 @@ -package token diff --git a/pkg/apiserver/authentication/token/user.go b/pkg/apiserver/authentication/token/user.go new file mode 100644 index 000000000..ccae5b6ee --- /dev/null +++ b/pkg/apiserver/authentication/token/user.go @@ -0,0 +1,27 @@ +/* + * + * 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 token + +type User interface { + // Name + GetName() string + + // UID + GetUID() string +} diff --git a/pkg/apiserver/authorization/authorizerfactory/opa_test.go b/pkg/apiserver/authorization/authorizerfactory/opa_test.go index 34d6e91da..3781dfae8 100644 --- a/pkg/apiserver/authorization/authorizerfactory/opa_test.go +++ b/pkg/apiserver/authorization/authorizerfactory/opa_test.go @@ -22,7 +22,6 @@ import ( "k8s.io/apiserver/pkg/authentication/user" "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer" "kubesphere.io/kubesphere/pkg/models/iam/am" - "kubesphere.io/kubesphere/pkg/simple/client/cache" "testing" ) @@ -46,7 +45,7 @@ resources_in_cluster1 { }, } - operator := am.NewFakeAMOperator(cache.NewSimpleCache()) + operator := am.NewFakeAMOperator() operator.Prepare(platformRoles, nil, nil, nil) opa := NewOPAAuthorizer(operator) diff --git a/pkg/kapis/iam/v1alpha2/handler.go b/pkg/kapis/iam/v1alpha2/handler.go index c5ff879c7..6f53f3070 100644 --- a/pkg/kapis/iam/v1alpha2/handler.go +++ b/pkg/kapis/iam/v1alpha2/handler.go @@ -19,7 +19,7 @@ type iamHandler struct { func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *iamHandler { return &iamHandler{ amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()), - imOperator: im.NewIMOperator(ldapClient, cacheClient, options), + imOperator: im.NewLDAPOperator(ldapClient), } } @@ -48,6 +48,7 @@ func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) } func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) { + panic("implement me") } func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) { diff --git a/pkg/kapis/iam/v1alpha2/register.go b/pkg/kapis/iam/v1alpha2/register.go index 5f37eb876..f41652a9e 100644 --- a/pkg/kapis/iam/v1alpha2/register.go +++ b/pkg/kapis/iam/v1alpha2/register.go @@ -44,13 +44,13 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer handler := newIAMHandler(k8sClient, factory, ldapClient, cacheClient, options) // implemented by create CRD object. - ws.Route(ws.POST("/users")) - ws.Route(ws.DELETE("/users/{user}")) - ws.Route(ws.PUT("/users/{user}")) - ws.Route(ws.GET("/users/{user}")) + //ws.Route(ws.POST("/users")) + //ws.Route(ws.DELETE("/users/{user}")) + //ws.Route(ws.PUT("/users/{user}")) + //ws.Route(ws.GET("/users/{user}")) // TODO move to resources api - ws.Route(ws.GET("/users")) + //ws.Route(ws.GET("/users")) ws.Route(ws.GET("/namespaces/{namespace}/roles"). To(handler.ListRoles). @@ -66,7 +66,7 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) // TODO merge - ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users")) + //ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/users")) ws.Route(ws.GET("/namespaces/{namespace}/users"). To(handler.ListNamespaceUsers). Doc("List all users in the specified namespace."). diff --git a/pkg/kapis/oauth/handler.go b/pkg/kapis/oauth/handler.go index 6213b5fde..e78260978 100644 --- a/pkg/kapis/oauth/handler.go +++ b/pkg/kapis/oauth/handler.go @@ -19,19 +19,25 @@ package oauth import ( + "fmt" "github.com/emicklei/go-restful" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api/auth" - "kubesphere.io/kubesphere/pkg/api/auth/token" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" + "kubesphere.io/kubesphere/pkg/apiserver/request" + "net/http" ) type oauthHandler struct { issuer token.Issuer + config oauth.Configuration } -func newOAUTHHandler(issuer token.Issuer) *oauthHandler { - return &oauthHandler{issuer: issuer} +func newOAUTHHandler(issuer token.Issuer, config oauth.Configuration) *oauthHandler { + return &oauthHandler{issuer: issuer, config: config} } // Implement webhook authentication interface @@ -53,7 +59,7 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re return } - user, err := h.issuer.Verify(tokenReview.Spec.Token) + user, _, err := h.issuer.Verify(tokenReview.Spec.Token) if err != nil { klog.Errorln(err) @@ -71,3 +77,45 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re resp.WriteEntity(success) } + +func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Response) { + user, ok := request.UserFrom(req.Request.Context()) + clientId := req.QueryParameter("client_id") + responseType := req.QueryParameter("response_type") + + conf, err := h.config.Load(clientId) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + return + } + + if responseType != "token" { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: response type %s is not supported", responseType)) + resp.WriteError(http.StatusUnauthorized, err) + return + } + + if !ok { + err := apierrors.NewUnauthorized("Unauthorized") + resp.WriteError(http.StatusUnauthorized, err) + return + } + + accessToken, clm, err := h.issuer.IssueTo(user) + + if err != nil { + err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) + resp.WriteError(http.StatusUnauthorized, err) + return + } + + redirectURL := fmt.Sprintf("%s?access_token=%s&token_type=Bearer", conf.RedirectURL, accessToken) + expiresIn := clm.ExpiresAt - clm.IssuedAt + if expiresIn > 0 { + redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn) + } + + http.Redirect(resp, req.Request, redirectURL, http.StatusFound) +} diff --git a/pkg/kapis/oauth/register.go b/pkg/kapis/oauth/register.go index 1ed2555ab..846cb8420 100644 --- a/pkg/kapis/oauth/register.go +++ b/pkg/kapis/oauth/register.go @@ -23,18 +23,19 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi" "kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api/auth" - "kubesphere.io/kubesphere/pkg/api/auth/token" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth" + "kubesphere.io/kubesphere/pkg/apiserver/authentication/token" "kubesphere.io/kubesphere/pkg/constants" "net/http" ) -func AddToContainer(c *restful.Container, options *auth.AuthenticationOptions) error { - ws := restful.WebService{} +func AddToContainer(c *restful.Container, issuer token.Issuer, configuration oauth.Configuration) error { + ws := &restful.WebService{} ws.Path("/oauth"). Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) - handler := newOAUTHHandler(token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(options.JwtSecret))) + handler := newOAUTHHandler(issuer, configuration) // Implement webhook authentication interface // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication @@ -46,16 +47,17 @@ func AddToContainer(c *restful.Container, options *auth.AuthenticationOptions) e Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag})) // TODO Built-in oauth2 server (provider) - // Low priority - c.Add(ws.Route(ws.POST("/authorize"))) - // web console use 'Resource Owner Password Credentials Grant' or 'Client Credentials Grant' request for an OAuth token // https://tools.ietf.org/html/rfc6749#section-4.3 // https://tools.ietf.org/html/rfc6749#section-4.4 - c.Add(ws.Route(ws.POST("/token"))) - // oauth2 client callback - c.Add(ws.Route(ws.POST("/callback/{callback}"))) + // curl -u admin:P@88w0rd 'http://ks-apiserver.kubesphere-system.svc/oauth/authorize?client_id=kubesphere-console-client&response_type=token' -v + ws.Route(ws.GET("/authorize"). + To(handler.AuthorizeHandler)) + //ws.Route(ws.POST("/token")) + //ws.Route(ws.POST("/callback/{callback}")) + + c.Add(ws) return nil } diff --git a/pkg/models/iam/am/fake_operator.go b/pkg/models/iam/am/fake_operator.go index 94be3a566..13a5c5253 100644 --- a/pkg/models/iam/am/fake_operator.go +++ b/pkg/models/iam/am/fake_operator.go @@ -21,6 +21,7 @@ package am import ( "encoding/json" "fmt" + "k8s.io/apiserver/pkg/authentication/user" "kubesphere.io/kubesphere/pkg/simple/client/cache" ) @@ -124,6 +125,15 @@ func (f FakeRole) GetRego() string { return f.Rego } -func NewFakeAMOperator(cache cache.Interface) *FakeOperator { - return &FakeOperator{cache: cache} +func NewFakeAMOperator() *FakeOperator { + operator := &FakeOperator{cache: cache.NewSimpleCache()} + operator.saveFakeRole(platformRoleCacheKey("admin"), FakeRole{ + Name: "admin", + Rego: "package authz\ndefault allow = true", + }) + operator.saveFakeRole(platformRoleCacheKey(user.Anonymous), FakeRole{ + Name: "admin", + Rego: "package authz\ndefault allow = false", + }) + return operator } diff --git a/pkg/models/iam/im/fake_operator.go b/pkg/models/iam/im/fake_operator.go new file mode 100644 index 000000000..e6186e044 --- /dev/null +++ b/pkg/models/iam/im/fake_operator.go @@ -0,0 +1,25 @@ +/* + * + * 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 "kubesphere.io/kubesphere/pkg/simple/client/ldap" + +func NewFakeOperator() IdentityManagementInterface { + return NewLDAPOperator(ldap.NewSimpleLdap()) +} diff --git a/pkg/models/iam/im/im.go b/pkg/models/iam/im/im.go index 07720d661..e9061c67e 100644 --- a/pkg/models/iam/im/im.go +++ b/pkg/models/iam/im/im.go @@ -18,19 +18,9 @@ package im import ( - "fmt" "github.com/pkg/errors" - "golang.org/x/oauth2" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/klog" - "kubesphere.io/kubesphere/pkg/api/auth" - "kubesphere.io/kubesphere/pkg/api/auth/token" "kubesphere.io/kubesphere/pkg/api/iam" - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/server/params" - "kubesphere.io/kubesphere/pkg/simple/client/cache" "kubesphere.io/kubesphere/pkg/simple/client/ldap" - "time" ) type IdentityManagementInterface interface { @@ -38,17 +28,11 @@ type IdentityManagementInterface interface { DeleteUser(username string) error ModifyUser(user *iam.User) (*iam.User, error) DescribeUser(username string) (*iam.User, error) - Login(username, password, ip string) (*oauth2.Token, error) - ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) - GetUserRoles(username string) ([]*rbacv1.Role, error) - GetUserRole(namespace string, username string) (*rbacv1.Role, error) + Authenticate(username, password string) (*iam.User, error) } type imOperator struct { - authenticateOptions *auth.AuthenticationOptions - ldapClient ldap.Interface - cacheClient cache.Interface - issuer token.Issuer + ldapClient ldap.Interface } var ( @@ -57,164 +41,54 @@ var ( UserNotExists = errors.New("user not exists") ) -func NewIMOperator(ldapClient ldap.Interface, cacheClient cache.Interface, options *auth.AuthenticationOptions) *imOperator { +func NewLDAPOperator(ldapClient ldap.Interface) IdentityManagementInterface { return &imOperator{ - ldapClient: ldapClient, - cacheClient: cacheClient, - authenticateOptions: options, - issuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(options.JwtSecret)), + ldapClient: ldapClient, } } func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) { + err := im.ldapClient.Update(user) + if err != nil { return nil, err } - // clear auth failed record - if user.Password != "" { - records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(user.Name, "*")) - if err == nil { - im.cacheClient.Del(records...) - } - } - return im.ldapClient.Get(user.Name) } -func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error) { - - records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(username, "*")) - if err != nil { - return nil, err - } - - if len(records) > im.authenticateOptions.MaxAuthenticateRetries { - return nil, AuthRateLimitExceeded - } +func (im *imOperator) Authenticate(username, password string) (*iam.User, error) { user, err := im.ldapClient.Get(username) + if err != nil { return nil, err } - err = im.ldapClient.Verify(user.Name, password) - if err != nil { - if err == ldap.ErrInvalidCredentials { - im.cacheClient.Set(authenticationFailedKeyForUsername(username, fmt.Sprintf("%d", time.Now().UnixNano())), "", 30*time.Minute) - } - return nil, err - } - - issuedToken, err := im.issuer.IssueTo(user) + err = im.ldapClient.Authenticate(user.Name, password) if err != nil { return nil, err } - // TODO: I think we should come up with a better strategy to prevent multiple login. - tokenKey := tokenKeyForUsername(user.Name, issuedToken) - if !im.authenticateOptions.MultipleLogin { - // multi login not allowed, remove the previous token - sessions, err := im.cacheClient.Keys(tokenKey) - if err != nil { - return nil, err - } - - if len(sessions) > 0 { - klog.V(4).Infoln("revoke token", sessions) - err = im.cacheClient.Del(sessions...) - if err != nil { - return nil, err - } - } - } - - // save token with expiration time - if err = im.cacheClient.Set(tokenKey, issuedToken, im.authenticateOptions.TokenExpiration); err != nil { - return nil, err - } - - im.logLogin(user.Name, ip, time.Now()) - - return &oauth2.Token{AccessToken: issuedToken}, nil -} - -func (im *imOperator) logLogin(username, ip string, loginTime time.Time) { - if ip != "" { - _ = im.cacheClient.Set(loginKeyForUsername(username, loginTime.UTC().Format("2006-01-02T15:04:05Z"), ip), "", 30*24*time.Hour) - } -} - -func (im *imOperator) LoginHistory(username string) ([]string, error) { - keys, err := im.cacheClient.Keys(loginKeyForUsername(username, "*", "*")) - if err != nil { - return nil, err - } - - return keys, nil -} - -func (im *imOperator) ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { - panic("implement me") + return user, nil } func (im *imOperator) DescribeUser(username string) (*iam.User, error) { return im.ldapClient.Get(username) } -func (im *imOperator) getLastLoginTime(username string) string { - return "" -} - func (im *imOperator) DeleteUser(username string) error { return im.ldapClient.Delete(username) } func (im *imOperator) CreateUser(user *iam.User) (*iam.User, error) { err := im.ldapClient.Create(user) + if err != nil { return nil, err } return user, nil } - -func (im *imOperator) VerifyToken(tokenString string) (*iam.User, error) { - providedUser, err := im.issuer.Verify(tokenString) - if err != nil { - return nil, err - } - - user, err := im.ldapClient.Get(providedUser.GetName()) - if err != nil { - return nil, err - } - - return user, nil -} - -func (im *imOperator) uidNumberNext() int { - // TODO fix me - return 0 -} -func (im *imOperator) GetUserRoles(username string) ([]*rbacv1.Role, error) { - panic("implement me") -} - -func (im *imOperator) GetUserRole(namespace string, username string) (*rbacv1.Role, error) { - panic("implement me") -} - -func authenticationFailedKeyForUsername(username, failedTimestamp string) string { - return fmt.Sprintf("kubesphere:authfailed:%s:%s", username, failedTimestamp) -} - -func tokenKeyForUsername(username, token string) string { - return fmt.Sprintf("kubesphere:users:%s:token:%s", username, token) -} - -func loginKeyForUsername(username, loginTimestamp, ip string) string { - return fmt.Sprintf("kubesphere:users:%s:login-log:%s:%s", username, loginTimestamp, ip) -} diff --git a/pkg/simple/client/ldap/interface.go b/pkg/simple/client/ldap/interface.go index 5b3e3e912..1f780d012 100644 --- a/pkg/simple/client/ldap/interface.go +++ b/pkg/simple/client/ldap/interface.go @@ -16,6 +16,6 @@ type Interface interface { // Get gets a user by its username from ldap, return ErrUserNotExists if user not exists Get(name string) (*iam.User, error) - // Verify checks if (name, password) is valid, return ErrInvalidCredentials if not - Verify(name string, password string) error + // Authenticate checks if (name, password) is valid, return ErrInvalidCredentials if not + Authenticate(name string, password string) error } diff --git a/pkg/simple/client/ldap/ldap.go b/pkg/simple/client/ldap/ldap.go index d228e58fb..bb88aced7 100644 --- a/pkg/simple/client/ldap/ldap.go +++ b/pkg/simple/client/ldap/ldap.go @@ -347,7 +347,7 @@ func (l *ldapInterfaceImpl) Update(newUser *iam.User) error { } -func (l *ldapInterfaceImpl) Verify(username, password string) error { +func (l *ldapInterfaceImpl) Authenticate(username, password string) error { conn, err := l.newConn() if err != nil { return err diff --git a/pkg/simple/client/ldap/simple_ldap.go b/pkg/simple/client/ldap/simple_ldap.go index deedd46a4..2dc8fe8bf 100644 --- a/pkg/simple/client/ldap/simple_ldap.go +++ b/pkg/simple/client/ldap/simple_ldap.go @@ -60,7 +60,7 @@ func (s simpleLdap) Get(name string) (*iam.User, error) { } } -func (s simpleLdap) Verify(name string, password string) error { +func (s simpleLdap) Authenticate(name string, password string) error { if user, err := s.Get(name); err != nil { return err } else { diff --git a/pkg/simple/client/ldap/simple_ldap_test.go b/pkg/simple/client/ldap/simple_ldap_test.go index c39144ba1..34dd9fb31 100644 --- a/pkg/simple/client/ldap/simple_ldap_test.go +++ b/pkg/simple/client/ldap/simple_ldap_test.go @@ -85,12 +85,12 @@ func TestSimpleLdap(t *testing.T) { t.Fatal(err) } - err = ldapClient.Verify(foo.Name, foo.Password) + err = ldapClient.Authenticate(foo.Name, foo.Password) if err != nil { t.Fatalf("should pass but got an error %v", err) } - err = ldapClient.Verify(foo.Name, "gibberish") + err = ldapClient.Authenticate(foo.Name, "gibberish") if err == nil || err != ErrInvalidCredentials { t.Fatalf("expected error ErrInvalidCrenentials but got %v", err) }