fix: password modify

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-06-28 11:18:25 +08:00
parent 52abbeb355
commit 77a3722b4e
15 changed files with 260 additions and 257 deletions

View File

@@ -38,6 +38,11 @@ type TokenReview struct {
Status *Status `json:"status,omitempty" description:"token review status"` Status *Status `json:"status,omitempty" description:"token review status"`
} }
type LoginRequest struct {
Username string `json:"username" description:"username"`
Password string `json:"password" description:"password"`
}
func (request *TokenReview) Validate() error { func (request *TokenReview) Validate() error {
if request.Spec == nil || request.Spec.Token == "" { if request.Spec == nil || request.Spec.Token == "" {
return fmt.Errorf("token must not be null") return fmt.Errorf("token must not be null")

View File

@@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package im package iam
import "kubesphere.io/kubesphere/pkg/simple/client/ldap" type PasswordReset struct {
CurrentPassword string `json:"currentPassword"`
func NewFakeOperator() IdentityManagementInterface { Password string `json:"password"`
return NewLDAPOperator(ldap.NewSimpleLdap())
} }

View File

@@ -183,12 +183,11 @@ func (s *APIServer) installKubeSphereAPIs() {
s.Config.MultiClusterOptions.ProxyPublishService, s.Config.MultiClusterOptions.ProxyPublishService,
s.Config.MultiClusterOptions.ProxyPublishAddress, s.Config.MultiClusterOptions.ProxyPublishAddress,
s.Config.MultiClusterOptions.AgentImage)) s.Config.MultiClusterOptions.AgentImage))
urlruntime.Must(iamapi.AddToContainer(s.container, imOperator := im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory, s.Config.AuthenticationOptions)
im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory), urlruntime.Must(iamapi.AddToContainer(s.container, imOperator,
am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()), am.NewOperator(s.InformerFactory, s.KubernetesClient.KubeSphere(), s.KubernetesClient.Kubernetes()),
s.Config.AuthenticationOptions)) s.Config.AuthenticationOptions))
urlruntime.Must(oauth.AddToContainer(s.container, urlruntime.Must(oauth.AddToContainer(s.container, imOperator,
im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory),
token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient),
s.Config.AuthenticationOptions)) s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container)) urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
@@ -275,7 +274,7 @@ func (s *APIServer) buildHandlerChain(stopCh <-chan struct{}) {
// authenticators are unordered // authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(), authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory))), basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory, s.Config.AuthenticationOptions))),
bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient)))) bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
handler = filters.WithAuthentication(handler, authn) handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver) handler = filters.WithRequestInfo(handler, requestInfoResolver)

View File

@@ -28,16 +28,11 @@ type AuthenticationOptions struct {
// authenticate rate limit will // authenticate rate limit will
AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"` AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"`
AuthenticateRateLimiterDuration time.Duration `json:"authenticationRateLimiterDuration" yaml:"authenticationRateLimiterDuration"` AuthenticateRateLimiterDuration time.Duration `json:"authenticationRateLimiterDuration" yaml:"authenticationRateLimiterDuration"`
// maximum retries when authenticate failed
MaxAuthenticateRetries int `json:"maxAuthenticateRetries" yaml:"maxAuthenticateRetries"`
// allow multiple users login at the same time // allow multiple users login at the same time
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"` MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
// secret to signed jwt token // secret to signed jwt token
JwtSecret string `json:"-" yaml:"jwtSecret"` JwtSecret string `json:"-" yaml:"jwtSecret"`
// oauth options
OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"` OAuthOptions *oauth.Options `json:"oauthOptions" yaml:"oauthOptions"`
} }
@@ -45,7 +40,6 @@ func NewAuthenticateOptions() *AuthenticationOptions {
return &AuthenticationOptions{ return &AuthenticationOptions{
AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterMaxTries: 5,
AuthenticateRateLimiterDuration: time.Minute * 30, AuthenticateRateLimiterDuration: time.Minute * 30,
MaxAuthenticateRetries: 0,
OAuthOptions: oauth.NewOptions(), OAuthOptions: oauth.NewOptions(),
MultipleLogin: false, MultipleLogin: false,
JwtSecret: "", JwtSecret: "",
@@ -64,7 +58,6 @@ func (options *AuthenticationOptions) Validate() []error {
func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *AuthenticationOptions) { func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *AuthenticationOptions) {
fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "") fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "")
fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "") fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "")
fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "")
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.") fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.") fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "AccessTokenMaxAgeSeconds control the lifetime of access tokens, 0 means no expiration.") fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "AccessTokenMaxAgeSeconds control the lifetime of access tokens, 0 means no expiration.")

View File

@@ -54,16 +54,14 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
} }
clm := &Claims{} clm := &Claims{}
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc) _, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 0 means no expiration. // accessTokenMaxAge = 0 or token without expiration time means that the token will not expire
// validate token cache // do not validate token cache
if s.options.OAuthOptions.AccessTokenMaxAge > 0 { if s.options.OAuthOptions.AccessTokenMaxAge > 0 && clm.ExpiresAt > 0 {
_, err = s.cache.Get(tokenCacheKey(tokenString)) _, err = s.cache.Get(tokenCacheKey(tokenString))
if err != nil { if err != nil {

View File

@@ -121,7 +121,6 @@ func newTestConfig() (*Config, error) {
AuthenticationOptions: &authoptions.AuthenticationOptions{ AuthenticationOptions: &authoptions.AuthenticationOptions{
AuthenticateRateLimiterMaxTries: 5, AuthenticateRateLimiterMaxTries: 5,
AuthenticateRateLimiterDuration: 30 * time.Minute, AuthenticateRateLimiterDuration: 30 * time.Minute,
MaxAuthenticateRetries: 6,
JwtSecret: "xxxxxx", JwtSecret: "xxxxxx",
MultipleLogin: false, MultipleLogin: false,
OAuthOptions: &oauth.Options{ OAuthOptions: &oauth.Options{

View File

@@ -18,6 +18,7 @@ package filters
import ( import (
"errors" "errors"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@@ -54,7 +55,7 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request) http.H
} }
gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} gv := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
responsewriters.ErrorNegotiated(apierrors.NewUnauthorized("Unauthorized"), s, gv, w, req) responsewriters.ErrorNegotiated(apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized:%s", err)), s, gv, w, req)
return return
} }

View File

@@ -205,7 +205,8 @@ func (r *ReconcileNamespace) bindWorkspace(namespace *corev1.Namespace) error {
} }
func removeWorkspaceOwnerReferences(ownerReferences []metav1.OwnerReference) []metav1.OwnerReference { func removeWorkspaceOwnerReferences(ownerReferences []metav1.OwnerReference) []metav1.OwnerReference {
for i, owner := range ownerReferences { for i := 0; i < len(ownerReferences); i++ {
owner := ownerReferences[i]
if owner.Kind == tenantv1alpha1.ResourceKindWorkspace { if owner.Kind == tenantv1alpha1.ResourceKindWorkspace {
ownerReferences = append(ownerReferences[:i], ownerReferences[i+1:]...) ownerReferences = append(ownerReferences[:i], ownerReferences[i+1:]...)
i-- i--

View File

@@ -5,11 +5,16 @@ import (
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/iam"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
apirequest "kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/models/iam/am" "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im" "kubesphere.io/kubesphere/pkg/models/iam/im"
servererr "kubesphere.io/kubesphere/pkg/server/errors" servererr "kubesphere.io/kubesphere/pkg/server/errors"
@@ -17,14 +22,16 @@ import (
) )
type iamHandler struct { type iamHandler struct {
am am.AccessManagementInterface am am.AccessManagementInterface
im im.IdentityManagementInterface im im.IdentityManagementInterface
authorizer authorizer.Authorizer
} }
func newIAMHandler(im im.IdentityManagementInterface, am am.AccessManagementInterface, options *authoptions.AuthenticationOptions) *iamHandler { func newIAMHandler(im im.IdentityManagementInterface, am am.AccessManagementInterface, options *authoptions.AuthenticationOptions) *iamHandler {
return &iamHandler{ return &iamHandler{
am: am, am: am,
im: im, im: im,
authorizer: authorizerfactory.NewRBACAuthorizer(am),
} }
} }
@@ -37,24 +44,19 @@ func (h *iamHandler) DescribeUser(request *restful.Request, response *restful.Re
username := request.PathParameter("user") username := request.PathParameter("user")
user, err := h.im.DescribeUser(username) user, err := h.im.DescribeUser(username)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
} }
globalRole, err := h.am.GetGlobalRoleOfUser(username) globalRole, err := h.am.GetGlobalRoleOfUser(username)
if err != nil && !errors.IsNotFound(err) { if err != nil && !errors.IsNotFound(err) {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
} }
if globalRole != nil { if globalRole != nil {
if user.Annotations == nil { user = appendGlobalRoleAnnotation(user, globalRole.Name)
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.GlobalRoleAnnotation] = globalRole.Name
} }
response.WriteEntity(user) response.WriteEntity(user)
@@ -66,7 +68,6 @@ func (h *iamHandler) RetrieveMemberRoleTemplates(request *restful.Request, respo
username := request.PathParameter("user") username := request.PathParameter("user")
globalRole, err := h.am.GetGlobalRoleOfUser(username) globalRole, err := h.am.GetGlobalRoleOfUser(username)
if err != nil { if err != nil {
// if role binding not exist return empty list // if role binding not exist return empty list
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
@@ -206,20 +207,23 @@ func (h *iamHandler) ListUsers(request *restful.Request, response *restful.Respo
} }
if globalRole != nil { if globalRole != nil {
if user.Annotations == nil { user = appendGlobalRoleAnnotation(user, globalRole.Name)
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.GlobalRoleAnnotation] = globalRole.Name
} }
result.Items[i] = user result.Items[i] = user
} }
response.WriteEntity(result) response.WriteEntity(result)
} }
func appendGlobalRoleAnnotation(user *iamv1alpha2.User, globalRole string) *iamv1alpha2.User {
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.GlobalRoleAnnotation] = globalRole
return user
}
func (h *iamHandler) ListRoles(request *restful.Request, response *restful.Response) { func (h *iamHandler) ListRoles(request *restful.Request, response *restful.Response) {
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -266,9 +270,7 @@ func (h *iamHandler) ListNamespaceMembers(request *restful.Request, response *re
} }
queryParam.Filters[iamv1alpha2.ScopeNamespace] = query.Value(namespace) queryParam.Filters[iamv1alpha2.ScopeNamespace] = query.Value(namespace)
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -280,7 +282,6 @@ func (h *iamHandler) ListNamespaceMembers(request *restful.Request, response *re
func (h *iamHandler) DescribeNamespaceMember(request *restful.Request, response *restful.Response) { func (h *iamHandler) DescribeNamespaceMember(request *restful.Request, response *restful.Response) {
username := request.PathParameter("member") username := request.PathParameter("member")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -292,7 +293,6 @@ func (h *iamHandler) DescribeNamespaceMember(request *restful.Request, response
queryParam.Filters[iamv1alpha2.ScopeNamespace] = query.Value(namespace) queryParam.Filters[iamv1alpha2.ScopeNamespace] = query.Value(namespace)
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -334,7 +334,6 @@ func (h *iamHandler) ListWorkspaceMembers(request *restful.Request, response *re
queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace) queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace)
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -352,7 +351,6 @@ func (h *iamHandler) DescribeWorkspaceMember(request *restful.Request, response
queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace) queryParam.Filters[iamv1alpha2.ScopeWorkspace] = query.Value(workspace)
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -372,9 +370,7 @@ func (h *iamHandler) UpdateWorkspaceRole(request *restful.Request, response *res
workspaceRoleName := request.PathParameter("workspacerole") workspaceRoleName := request.PathParameter("workspacerole")
var workspaceRole iamv1alpha2.WorkspaceRole var workspaceRole iamv1alpha2.WorkspaceRole
err := request.ReadEntity(&workspaceRole) err := request.ReadEntity(&workspaceRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -389,7 +385,6 @@ func (h *iamHandler) UpdateWorkspaceRole(request *restful.Request, response *res
} }
updated, err := h.am.CreateOrUpdateWorkspaceRole(workspace, &workspaceRole) updated, err := h.am.CreateOrUpdateWorkspaceRole(workspace, &workspaceRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -403,9 +398,7 @@ func (h *iamHandler) CreateWorkspaceRole(request *restful.Request, response *res
workspace := request.PathParameter("workspace") workspace := request.PathParameter("workspace")
var workspaceRole iamv1alpha2.WorkspaceRole var workspaceRole iamv1alpha2.WorkspaceRole
err := request.ReadEntity(&workspaceRole) err := request.ReadEntity(&workspaceRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -413,7 +406,6 @@ func (h *iamHandler) CreateWorkspaceRole(request *restful.Request, response *res
} }
created, err := h.am.CreateOrUpdateWorkspaceRole(workspace, &workspaceRole) created, err := h.am.CreateOrUpdateWorkspaceRole(workspace, &workspaceRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -428,7 +420,6 @@ func (h *iamHandler) DeleteWorkspaceRole(request *restful.Request, response *res
workspaceRoleName := request.PathParameter("workspacerole") workspaceRoleName := request.PathParameter("workspacerole")
err := h.am.DeleteWorkspaceRole(workspace, workspaceRoleName) err := h.am.DeleteWorkspaceRole(workspace, workspaceRoleName)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -510,22 +501,62 @@ func (h *iamHandler) UpdateUser(request *restful.Request, response *restful.Resp
return return
} }
if globalRole != "" { operator, ok := apirequest.UserFrom(request.Request.Context())
if err := h.am.CreateGlobalRoleBinding(user.Name, globalRole); err != nil {
if globalRole != "" && ok {
err = h.updateGlobalRoleBinding(operator, updated, globalRole)
if err != nil {
klog.Error(err)
handleError(request, response, err)
return
}
updated = appendGlobalRoleAnnotation(updated, globalRole)
}
response.WriteEntity(updated)
}
func (h *iamHandler) ModifyPassword(request *restful.Request, response *restful.Response) {
username := request.PathParameter("user")
var passwordReset iam.PasswordReset
err := request.ReadEntity(&passwordReset)
if err != nil {
klog.Error(err)
api.HandleBadRequest(response, request, err)
return
}
operator, ok := apirequest.UserFrom(request.Request.Context())
// change password by self
if ok && operator.GetName() == username {
_, err := h.im.Authenticate(username, passwordReset.CurrentPassword)
if err != nil {
if err == im.AuthFailedIncorrectPassword {
err = errors.NewForbidden(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularUser), username, err)
klog.Warning(err)
handleError(request, response, err)
return
}
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
return return
} }
} }
response.WriteEntity(updated) err = h.im.ModifyPassword(username, passwordReset.Password)
if err != nil {
klog.Error(err)
handleError(request, response, err)
return
}
response.WriteEntity(servererr.None)
} }
func (h *iamHandler) DeleteUser(request *restful.Request, response *restful.Response) { func (h *iamHandler) DeleteUser(request *restful.Request, response *restful.Response) {
username := request.PathParameter("user") username := request.PathParameter("user")
err := h.im.DeleteUser(username) err := h.im.DeleteUser(username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -540,7 +571,6 @@ func (h *iamHandler) CreateGlobalRole(request *restful.Request, response *restfu
var globalRole iamv1alpha2.GlobalRole var globalRole iamv1alpha2.GlobalRole
err := request.ReadEntity(&globalRole) err := request.ReadEntity(&globalRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -548,7 +578,6 @@ func (h *iamHandler) CreateGlobalRole(request *restful.Request, response *restfu
} }
created, err := h.am.CreateOrUpdateGlobalRole(&globalRole) created, err := h.am.CreateOrUpdateGlobalRole(&globalRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -562,7 +591,6 @@ func (h *iamHandler) DeleteGlobalRole(request *restful.Request, response *restfu
globalRole := request.PathParameter("globalrole") globalRole := request.PathParameter("globalrole")
err := h.am.DeleteGlobalRole(globalRole) err := h.am.DeleteGlobalRole(globalRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -578,7 +606,6 @@ func (h *iamHandler) UpdateGlobalRole(request *restful.Request, response *restfu
var globalRole iamv1alpha2.GlobalRole var globalRole iamv1alpha2.GlobalRole
err := request.ReadEntity(&globalRole) err := request.ReadEntity(&globalRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -593,7 +620,6 @@ func (h *iamHandler) UpdateGlobalRole(request *restful.Request, response *restfu
} }
updated, err := h.am.CreateOrUpdateGlobalRole(&globalRole) updated, err := h.am.CreateOrUpdateGlobalRole(&globalRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -611,15 +637,12 @@ func (h *iamHandler) DescribeGlobalRole(request *restful.Request, response *rest
handleError(request, response, err) handleError(request, response, err)
return return
} }
response.WriteEntity(globalRole) response.WriteEntity(globalRole)
} }
func (h *iamHandler) CreateClusterRole(request *restful.Request, response *restful.Response) { func (h *iamHandler) CreateClusterRole(request *restful.Request, response *restful.Response) {
var clusterRole rbacv1.ClusterRole var clusterRole rbacv1.ClusterRole
err := request.ReadEntity(&clusterRole) err := request.ReadEntity(&clusterRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -627,7 +650,6 @@ func (h *iamHandler) CreateClusterRole(request *restful.Request, response *restf
} }
created, err := h.am.CreateOrUpdateClusterRole(&clusterRole) created, err := h.am.CreateOrUpdateClusterRole(&clusterRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -641,7 +663,6 @@ func (h *iamHandler) DeleteClusterRole(request *restful.Request, response *restf
clusterrole := request.PathParameter("clusterrole") clusterrole := request.PathParameter("clusterrole")
err := h.am.DeleteClusterRole(clusterrole) err := h.am.DeleteClusterRole(clusterrole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -657,7 +678,6 @@ func (h *iamHandler) UpdateClusterRole(request *restful.Request, response *restf
var clusterRole rbacv1.ClusterRole var clusterRole rbacv1.ClusterRole
err := request.ReadEntity(&clusterRole) err := request.ReadEntity(&clusterRole)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -672,7 +692,6 @@ func (h *iamHandler) UpdateClusterRole(request *restful.Request, response *restf
} }
updated, err := h.am.CreateOrUpdateClusterRole(&clusterRole) updated, err := h.am.CreateOrUpdateClusterRole(&clusterRole)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -690,7 +709,6 @@ func (h *iamHandler) DescribeClusterRole(request *restful.Request, response *res
handleError(request, response, err) handleError(request, response, err)
return return
} }
response.WriteEntity(clusterRole) response.WriteEntity(clusterRole)
} }
@@ -703,14 +721,12 @@ func (h *iamHandler) DescribeWorkspaceRole(request *restful.Request, response *r
handleError(request, response, err) handleError(request, response, err)
return return
} }
response.WriteEntity(workspaceRole) response.WriteEntity(workspaceRole)
} }
func (h *iamHandler) CreateNamespaceRole(request *restful.Request, response *restful.Response) { func (h *iamHandler) CreateNamespaceRole(request *restful.Request, response *restful.Response) {
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -718,9 +734,7 @@ func (h *iamHandler) CreateNamespaceRole(request *restful.Request, response *res
} }
var role rbacv1.Role var role rbacv1.Role
err = request.ReadEntity(&role) err = request.ReadEntity(&role)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -728,7 +742,6 @@ func (h *iamHandler) CreateNamespaceRole(request *restful.Request, response *res
} }
created, err := h.am.CreateOrUpdateNamespaceRole(namespace, &role) created, err := h.am.CreateOrUpdateNamespaceRole(namespace, &role)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -739,10 +752,9 @@ func (h *iamHandler) CreateNamespaceRole(request *restful.Request, response *res
} }
func (h *iamHandler) DeleteNamespaceRole(request *restful.Request, response *restful.Response) { func (h *iamHandler) DeleteNamespaceRole(request *restful.Request, response *restful.Response) {
role := request.PathParameter("role") role := request.PathParameter("role")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -750,7 +762,6 @@ func (h *iamHandler) DeleteNamespaceRole(request *restful.Request, response *res
} }
err = h.am.DeleteNamespaceRole(namespace, role) err = h.am.DeleteNamespaceRole(namespace, role)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -761,10 +772,9 @@ func (h *iamHandler) DeleteNamespaceRole(request *restful.Request, response *res
} }
func (h *iamHandler) UpdateNamespaceRole(request *restful.Request, response *restful.Response) { func (h *iamHandler) UpdateNamespaceRole(request *restful.Request, response *restful.Response) {
roleName := request.PathParameter("role") roleName := request.PathParameter("role")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -772,9 +782,7 @@ func (h *iamHandler) UpdateNamespaceRole(request *restful.Request, response *res
} }
var role rbacv1.Role var role rbacv1.Role
err = request.ReadEntity(&role) err = request.ReadEntity(&role)
if err != nil { if err != nil {
klog.Errorf("%+v", err) klog.Errorf("%+v", err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -789,7 +797,6 @@ func (h *iamHandler) UpdateNamespaceRole(request *restful.Request, response *res
} }
updated, err := h.am.CreateOrUpdateNamespaceRole(namespace, &role) updated, err := h.am.CreateOrUpdateNamespaceRole(namespace, &role)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -803,9 +810,7 @@ func (h *iamHandler) CreateWorkspaceMembers(request *restful.Request, response *
workspace := request.PathParameter("workspace") workspace := request.PathParameter("workspace")
var members []Member var members []Member
err := request.ReadEntity(&members) err := request.ReadEntity(&members)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -829,7 +834,6 @@ func (h *iamHandler) RemoveWorkspaceMember(request *restful.Request, response *r
username := request.PathParameter("workspacemember") username := request.PathParameter("workspacemember")
err := h.am.RemoveUserFromWorkspace(username, workspace) err := h.am.RemoveUserFromWorkspace(username, workspace)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -844,9 +848,7 @@ func (h *iamHandler) UpdateWorkspaceMember(request *restful.Request, response *r
username := request.PathParameter("workspacemember") username := request.PathParameter("workspacemember")
var member Member var member Member
err := request.ReadEntity(&member) err := request.ReadEntity(&member)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -873,7 +875,6 @@ func (h *iamHandler) UpdateWorkspaceMember(request *restful.Request, response *r
func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response *restful.Response) { func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response *restful.Response) {
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -881,9 +882,7 @@ func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response *
} }
var members []Member var members []Member
err = request.ReadEntity(&members) err = request.ReadEntity(&members)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -904,8 +903,8 @@ func (h *iamHandler) CreateNamespaceMembers(request *restful.Request, response *
func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *restful.Response) { func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *restful.Response) {
username := request.PathParameter("member") username := request.PathParameter("member")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -913,9 +912,7 @@ func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *r
} }
var member Member var member Member
err = request.ReadEntity(&member) err = request.ReadEntity(&member)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -941,8 +938,8 @@ func (h *iamHandler) UpdateNamespaceMember(request *restful.Request, response *r
func (h *iamHandler) RemoveNamespaceMember(request *restful.Request, response *restful.Response) { func (h *iamHandler) RemoveNamespaceMember(request *restful.Request, response *restful.Response) {
username := request.PathParameter("member") username := request.PathParameter("member")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -950,7 +947,6 @@ func (h *iamHandler) RemoveNamespaceMember(request *restful.Request, response *r
} }
err = h.am.RemoveUserFromNamespace(username, namespace) err = h.am.RemoveUserFromNamespace(username, namespace)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -964,7 +960,6 @@ func (h *iamHandler) CreateClusterMembers(request *restful.Request, response *re
var members []Member var members []Member
err := request.ReadEntity(&members) err := request.ReadEntity(&members)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -987,7 +982,6 @@ func (h *iamHandler) RemoveClusterMember(request *restful.Request, response *res
username := request.PathParameter("clustermember") username := request.PathParameter("clustermember")
err := h.am.RemoveUserFromCluster(username) err := h.am.RemoveUserFromCluster(username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -1001,9 +995,7 @@ func (h *iamHandler) UpdateClusterMember(request *restful.Request, response *res
username := request.PathParameter("clustermember") username := request.PathParameter("clustermember")
var member Member var member Member
err := request.ReadEntity(&member) err := request.ReadEntity(&member)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -1035,7 +1027,6 @@ func (h *iamHandler) DescribeClusterMember(request *restful.Request, response *r
queryParam.Filters[iamv1alpha2.ScopeCluster] = "true" queryParam.Filters[iamv1alpha2.ScopeCluster] = "true"
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -1052,11 +1043,9 @@ func (h *iamHandler) DescribeClusterMember(request *restful.Request, response *r
func (h *iamHandler) ListClusterMembers(request *restful.Request, response *restful.Response) { func (h *iamHandler) ListClusterMembers(request *restful.Request, response *restful.Response) {
queryParam := query.ParseQueryParameter(request) queryParam := query.ParseQueryParameter(request)
queryParam.Filters[iamv1alpha2.ScopeCluster] = "true" queryParam.Filters[iamv1alpha2.ScopeCluster] = "true"
result, err := h.im.ListUsers(queryParam) result, err := h.im.ListUsers(queryParam)
if err != nil { if err != nil {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
return return
@@ -1066,10 +1055,8 @@ func (h *iamHandler) ListClusterMembers(request *restful.Request, response *rest
} }
func (h *iamHandler) DescribeNamespaceRole(request *restful.Request, response *restful.Response) { func (h *iamHandler) DescribeNamespaceRole(request *restful.Request, response *restful.Response) {
roleName := request.PathParameter("role") roleName := request.PathParameter("role")
namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops")) namespace, err := h.resolveNamespace(request.PathParameter("namespace"), request.PathParameter("devops"))
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -1077,7 +1064,6 @@ func (h *iamHandler) DescribeNamespaceRole(request *restful.Request, response *r
} }
role, err := h.am.GetNamespaceRole(namespace, roleName) role, err := h.am.GetNamespaceRole(namespace, roleName)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
handleError(request, response, err) handleError(request, response, err)
@@ -1108,9 +1094,7 @@ func (h *iamHandler) PatchWorkspaceRole(request *restful.Request, response *rest
} }
workspaceRole.Name = workspaceRoleName workspaceRole.Name = workspaceRoleName
patched, err := h.am.PatchWorkspaceRole(workspaceName, &workspaceRole) patched, err := h.am.PatchWorkspaceRole(workspaceName, &workspaceRole)
if err != nil { if err != nil {
handleError(request, response, err) handleError(request, response, err)
return return
@@ -1131,9 +1115,7 @@ func (h *iamHandler) PatchGlobalRole(request *restful.Request, response *restful
} }
globalRole.Name = globalRoleName globalRole.Name = globalRoleName
patched, err := h.am.PatchGlobalRole(&globalRole) patched, err := h.am.PatchGlobalRole(&globalRole)
if err != nil { if err != nil {
handleError(request, response, err) handleError(request, response, err)
return return
@@ -1160,9 +1142,7 @@ func (h *iamHandler) PatchNamespaceRole(request *restful.Request, response *rest
} }
role.Name = roleName role.Name = roleName
patched, err := h.am.PatchNamespaceRole(namespaceName, &role) patched, err := h.am.PatchNamespaceRole(namespaceName, &role)
if err != nil { if err != nil {
handleError(request, response, err) handleError(request, response, err)
return return
@@ -1183,9 +1163,7 @@ func (h *iamHandler) PatchClusterRole(request *restful.Request, response *restfu
} }
clusterRole.Name = clusterRoleName clusterRole.Name = clusterRoleName
patched, err := h.am.PatchClusterRole(&clusterRole) patched, err := h.am.PatchClusterRole(&clusterRole)
if err != nil { if err != nil {
handleError(request, response, err) handleError(request, response, err)
return return
@@ -1194,6 +1172,31 @@ func (h *iamHandler) PatchClusterRole(request *restful.Request, response *restfu
response.WriteEntity(patched) response.WriteEntity(patched)
} }
func (h *iamHandler) updateGlobalRoleBinding(operator user.Info, user *iamv1alpha2.User, globalRole string) error {
userManagement := authorizer.AttributesRecord{
Resource: iamv1alpha2.ResourcesPluralUser,
Verb: "update",
ResourceScope: apirequest.GlobalScope,
ResourceRequest: true,
User: operator,
}
decision, _, err := h.authorizer.Authorize(userManagement)
if err != nil {
klog.Error(err)
return err
}
if decision != authorizer.DecisionAllow {
err = errors.NewForbidden(iamv1alpha2.Resource(iamv1alpha2.ResourcesSingularUser), user.Name, fmt.Errorf("update global role binding not allowed"))
klog.Warning(err)
return err
}
if err := h.am.CreateGlobalRoleBinding(user.Name, globalRole); err != nil {
klog.Error(err)
return err
}
return nil
}
func handleError(request *restful.Request, response *restful.Response, err error) { func handleError(request *restful.Request, response *restful.Response, err error) {
if errors.IsBadRequest(err) { if errors.IsBadRequest(err) {
api.HandleBadRequest(response, request, err) api.HandleBadRequest(response, request, err)
@@ -1201,6 +1204,8 @@ func handleError(request *restful.Request, response *restful.Response, err error
api.HandleNotFound(response, request, err) api.HandleNotFound(response, request, err)
} else if errors.IsAlreadyExists(err) { } else if errors.IsAlreadyExists(err) {
api.HandleConflict(response, request, err) api.HandleConflict(response, request, err)
} else if errors.IsForbidden(err) {
api.HandleForbidden(response, request, err)
} else { } else {
api.HandleInternalError(response, request, err) api.HandleInternalError(response, request, err)
} }

View File

@@ -22,6 +22,7 @@ import (
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/api/iam"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options" authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime" "kubesphere.io/kubesphere/pkg/apiserver/runtime"
@@ -62,6 +63,13 @@ func AddToContainer(container *restful.Container, im im.IdentityManagementInterf
Param(ws.PathParameter("user", "username")). Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}). Returns(http.StatusOK, api.StatusOK, iamv1alpha2.User{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag})) Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.PUT("/users/{user}/password").
To(handler.ModifyPassword).
Doc("Modify user's password.").
Reads(iam.PasswordReset{}).
Param(ws.PathParameter("user", "username")).
Returns(http.StatusOK, api.StatusOK, errors.None).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/users/{user}"). ws.Route(ws.GET("/users/{user}").
To(handler.DescribeUser). To(handler.DescribeUser).
Doc("Retrieve user details."). Doc("Retrieve user details.").

View File

@@ -90,7 +90,6 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
redirectURI := req.QueryParameter("redirect_uri") redirectURI := req.QueryParameter("redirect_uri")
conf, err := h.options.OAuthOptions.OAuthClient(clientId) conf, err := h.options.OAuthOptions.OAuthClient(clientId)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) resp.WriteError(http.StatusUnauthorized, err)
@@ -109,14 +108,7 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
return return
} }
expiresIn := h.options.OAuthOptions.AccessTokenMaxAge token, err := h.issueTo(user.GetName())
if conf.AccessTokenMaxAge != nil {
expiresIn = *conf.AccessTokenMaxAge
}
accessToken, err := h.issuer.IssueTo(user, expiresIn)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) resp.WriteError(http.StatusUnauthorized, err)
@@ -131,12 +123,11 @@ func (h *oauthHandler) AuthorizeHandler(req *restful.Request, resp *restful.Resp
return return
} }
redirectURL = fmt.Sprintf("%s#access_token=%s&token_type=Bearer", redirectURL, accessToken) redirectURL = fmt.Sprintf("%s#access_token=%s&token_type=Bearer", redirectURL, token.AccessToken)
if expiresIn > 0 { if token.ExpiresIn > 0 {
redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, expiresIn.Seconds()) redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, token.ExpiresIn)
} }
resp.Header().Set("Content-Type", "text/plain") resp.Header().Set("Content-Type", "text/plain")
http.Redirect(resp, req.Request, redirectURL, http.StatusFound) http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
} }
@@ -212,23 +203,57 @@ func (h *oauthHandler) OAuthCallBackHandler(req *restful.Request, resp *restful.
return return
} }
expiresIn := h.options.OAuthOptions.AccessTokenMaxAge result, err := h.issueTo(user.GetName())
accessToken, err := h.issuer.IssueTo(&authuser.DefaultInfo{
Name: user.GetName(),
}, expiresIn)
if err != nil { if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)) err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
resp.WriteError(http.StatusUnauthorized, err) resp.WriteError(http.StatusUnauthorized, err)
return return
} }
resp.WriteEntity(result)
}
result := oauth.Token{ func (h *oauthHandler) Login(request *restful.Request, response *restful.Response) {
var loginRequest auth.LoginRequest
err := request.ReadEntity(&loginRequest)
if err != nil || loginRequest.Username == "" || loginRequest.Password == "" {
response.WriteHeaderAndEntity(http.StatusUnauthorized, fmt.Errorf("empty username or password"))
return
}
user, err := h.im.Authenticate(loginRequest.Username, loginRequest.Password)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err)
return
}
result, err := h.issueTo(user.Name)
if err != nil {
err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
response.WriteError(http.StatusUnauthorized, err)
return
}
response.WriteEntity(result)
}
func (h *oauthHandler) issueTo(username string) (*oauth.Token, error) {
expiresIn := h.options.OAuthOptions.AccessTokenMaxAge
accessToken, err := h.issuer.IssueTo(&authuser.DefaultInfo{
Name: username,
}, expiresIn)
if err != nil {
klog.Error(err)
return nil, err
}
result := &oauth.Token{
AccessToken: accessToken, AccessToken: accessToken,
TokenType: "Bearer", TokenType: "Bearer",
ExpiresIn: int(expiresIn.Seconds()), ExpiresIn: int(expiresIn.Seconds()),
} }
resp.WriteEntity(result) return result, nil
} }

View File

@@ -90,5 +90,20 @@ func AddToContainer(c *restful.Container, im im.IdentityManagementInterface, iss
c.Add(ws) c.Add(ws)
// legacy auth API
legacy := &restful.WebService{}
legacy.Path("/kapis/iam.kubesphere.io/v1alpha2/login").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)
legacy.Route(legacy.POST("").
To(handler.Login).
Deprecate().
Doc("KubeSphere APIs support token-based authentication via the Authtoken request header. The POST Login API is used to retrieve the authentication token. After the authentication token is obtained, it must be inserted into the Authtoken header for all requests.").
Reads(auth.LoginRequest{}).
Returns(http.StatusOK, api.StatusOK, oauth.Token{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.IdentityManagementTag}))
c.Add(legacy)
return nil return nil
} }

View File

@@ -22,11 +22,13 @@ import (
"k8s.io/klog" "k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api" "kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/query" "kubesphere.io/kubesphere/pkg/apiserver/query"
kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned" kubesphereclient "kubesphere.io/kubesphere/pkg/client/clientset/versioned"
"kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/informers"
resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource" resourcev1alpha3 "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/resource"
"net/mail" "net/mail"
"time"
) )
type IdentityManagementInterface interface { type IdentityManagementInterface interface {
@@ -36,6 +38,7 @@ type IdentityManagementInterface interface {
UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error)
DescribeUser(username string) (*iamv1alpha2.User, error) DescribeUser(username string) (*iamv1alpha2.User, error)
Authenticate(username, password string) (*iamv1alpha2.User, error) Authenticate(username, password string) (*iamv1alpha2.User, error)
ModifyPassword(username string, password string) error
} }
var ( var (
@@ -45,34 +48,66 @@ var (
UserNotExists = errors.New("user not exists") UserNotExists = errors.New("user not exists")
) )
func NewOperator(ksClient kubesphereclient.Interface, factory informers.InformerFactory) IdentityManagementInterface { func NewOperator(ksClient kubesphereclient.Interface, factory informers.InformerFactory, options *authoptions.AuthenticationOptions) IdentityManagementInterface {
im := &defaultIMOperator{
return &defaultIMOperator{
ksClient: ksClient, ksClient: ksClient,
resourceGetter: resourcev1alpha3.NewResourceGetter(factory), resourceGetter: resourcev1alpha3.NewResourceGetter(factory),
} }
if options != nil {
im.authenticateRateLimiterDuration = options.AuthenticateRateLimiterDuration
im.authenticateRateLimiterMaxTries = options.AuthenticateRateLimiterMaxTries
}
return im
} }
type defaultIMOperator struct { type defaultIMOperator struct {
ksClient kubesphereclient.Interface ksClient kubesphereclient.Interface
resourceGetter *resourcev1alpha3.ResourceGetter resourceGetter *resourcev1alpha3.ResourceGetter
authenticateRateLimiterMaxTries int
authenticateRateLimiterDuration time.Duration
} }
func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) { func (im *defaultIMOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", user.Name) obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", user.Name)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
old := obj.(*iamv1alpha2.User).DeepCopy() old := obj.(*iamv1alpha2.User).DeepCopy()
if user.Annotations == nil {
user.Annotations = make(map[string]string, 0)
}
user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] user.Annotations[iamv1alpha2.PasswordEncryptedAnnotation] = old.Annotations[iamv1alpha2.PasswordEncryptedAnnotation]
user.Spec.EncryptedPassword = old.Spec.EncryptedPassword user.Spec.EncryptedPassword = old.Spec.EncryptedPassword
user.Status = old.Status user.Status = old.Status
return im.ksClient.IamV1alpha2().Users().Update(user) updated, err := im.ksClient.IamV1alpha2().Users().Update(user)
if err != nil {
klog.Error(err)
return nil, err
}
return ensurePasswordNotOutput(updated), nil
}
func (im *defaultIMOperator) ModifyPassword(username string, password string) error {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", 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)
return err
}
return nil
} }
func (im *defaultIMOperator) Authenticate(username, password string) (*iamv1alpha2.User, error) { func (im *defaultIMOperator) Authenticate(username, password string) (*iamv1alpha2.User, error) {
@@ -80,26 +115,21 @@ func (im *defaultIMOperator) Authenticate(username, password string) (*iamv1alph
var user *iamv1alpha2.User var user *iamv1alpha2.User
if _, err := mail.ParseAddress(username); err != nil { if _, err := mail.ParseAddress(username); err != nil {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username) obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
user = obj.(*iamv1alpha2.User) user = obj.(*iamv1alpha2.User)
} else { } else {
objs, err := im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", &query.Query{ objs, err := im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", &query.Query{
Pagination: query.NoPagination, Pagination: query.NoPagination,
Filters: map[query.Field]query.Value{iamv1alpha2.FieldEmail: query.Value(username)}, Filters: map[query.Field]query.Value{iamv1alpha2.FieldEmail: query.Value(username)},
}) })
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
if len(objs.Items) != 1 { if len(objs.Items) != 1 {
if len(objs.Items) == 0 { if len(objs.Items) == 0 {
klog.Warningf("username or email: %s not exist", username) klog.Warningf("username or email: %s not exist", username)
@@ -108,34 +138,36 @@ func (im *defaultIMOperator) Authenticate(username, password string) (*iamv1alph
} }
return nil, AuthFailedIncorrectPassword return nil, AuthFailedIncorrectPassword
} }
user = objs.Items[0].(*iamv1alpha2.User) user = objs.Items[0].(*iamv1alpha2.User)
} }
if im.authRateLimitExceeded(user) {
im.authFailRecord(user, AuthRateLimitExceeded)
return nil, AuthRateLimitExceeded
}
if checkPasswordHash(password, user.Spec.EncryptedPassword) { if checkPasswordHash(password, user.Spec.EncryptedPassword) {
return user, nil return user, nil
} }
im.authFailRecord(user, AuthFailedIncorrectPassword)
return nil, AuthFailedIncorrectPassword return nil, AuthFailedIncorrectPassword
} }
func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) { func (im *defaultIMOperator) ListUsers(query *query.Query) (result *api.ListResult, err error) {
result, err = im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", query) result, err = im.resourceGetter.List(iamv1alpha2.ResourcesPluralUser, "", query)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
items := make([]interface{}, 0) items := make([]interface{}, 0)
for _, item := range result.Items { for _, item := range result.Items {
user := item.(*iamv1alpha2.User) user := item.(*iamv1alpha2.User)
items = append(items, ensurePasswordNotOutput(user)) out := ensurePasswordNotOutput(user)
items = append(items, out)
} }
result.Items = items result.Items = items
return result, nil return result, nil
} }
@@ -146,14 +178,12 @@ func checkPasswordHash(password, hash string) bool {
func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) { func (im *defaultIMOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username) obj, err := im.resourceGetter.Get(iamv1alpha2.ResourcesPluralUser, "", username)
if err != nil { if err != nil {
klog.Error(err) klog.Error(err)
return nil, err return nil, err
} }
user := obj.(*iamv1alpha2.User) user := obj.(*iamv1alpha2.User)
return ensurePasswordNotOutput(user), nil return ensurePasswordNotOutput(user), nil
} }
@@ -170,6 +200,29 @@ func (im *defaultIMOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.Us
return user, nil return user, nil
} }
func (im *defaultIMOperator) authRateLimitEnabled() bool {
if im.authenticateRateLimiterMaxTries <= 0 || im.authenticateRateLimiterDuration == 0 {
return false
}
return true
}
func (im *defaultIMOperator) authRateLimitExceeded(user *iamv1alpha2.User) bool {
if !im.authRateLimitEnabled() {
return false
}
// TODO record login history using CRD
return false
}
func (im *defaultIMOperator) authFailRecord(user *iamv1alpha2.User, err error) {
if !im.authRateLimitEnabled() {
return
}
// TODO record login history using CRD
}
func ensurePasswordNotOutput(user *iamv1alpha2.User) *iamv1alpha2.User { func ensurePasswordNotOutput(user *iamv1alpha2.User) *iamv1alpha2.User {
out := user.DeepCopy() out := user.DeepCopy()
// ensure encrypted password will not be output // ensure encrypted password will not be output

View File

@@ -1,91 +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 (
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
)
type ldapOperator struct {
ldapClient ldap.Interface
}
func NewLDAPOperator(ldapClient ldap.Interface) IdentityManagementInterface {
return &ldapOperator{
ldapClient: ldapClient,
}
}
func (im *ldapOperator) UpdateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
err := im.ldapClient.Update(user)
if err != nil {
return nil, err
}
return im.ldapClient.Get(user.Name)
}
func (im *ldapOperator) Authenticate(username, password string) (*iamv1alpha2.User, error) {
user, err := im.ldapClient.Get(username)
if err != nil {
return nil, err
}
err = im.ldapClient.Authenticate(user.Name, password)
if err != nil {
return nil, err
}
return user, nil
}
func (im *ldapOperator) DescribeUser(username string) (*iamv1alpha2.User, error) {
return im.ldapClient.Get(username)
}
func (im *ldapOperator) DeleteUser(username string) error {
return im.ldapClient.Delete(username)
}
func (im *ldapOperator) CreateUser(user *iamv1alpha2.User) (*iamv1alpha2.User, error) {
err := im.ldapClient.Create(user)
if err != nil {
return nil, err
}
return user, nil
}
func (im *ldapOperator) ListUsers(query *query.Query) (*api.ListResult, error) {
result, err := im.ldapClient.List(query)
if err != nil {
klog.Error(err)
return nil, err
}
return result, nil
}

View File

@@ -43,10 +43,3 @@ type PodInfo struct {
Pod string `json:"pod" description:"pod name"` Pod string `json:"pod" description:"pod name"`
Container string `json:"container" description:"container name"` Container string `json:"container" description:"container name"`
} }
type AuthGrantResponse struct {
TokenType string `json:"token_type,omitempty"`
Token string `json:"access_token" description:"access token"`
ExpiresIn float64 `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
}