handler.go 10.4 KB
Newer Older
H
update  
hongming 已提交
1
/*
H
hongming 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15
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.
*/
H
update  
hongming 已提交
16 17 18 19

package oauth

import (
H
update  
hongming 已提交
20
	"fmt"
H
update  
hongming 已提交
21
	"github.com/emicklei/go-restful"
H
update  
hongming 已提交
22
	apierrors "k8s.io/apimachinery/pkg/api/errors"
H
hongming 已提交
23
	"k8s.io/apimachinery/pkg/labels"
Z
zryfish 已提交
24
	"k8s.io/apiserver/pkg/authentication/user"
H
update  
hongming 已提交
25 26
	"k8s.io/klog"
	"kubesphere.io/kubesphere/pkg/api"
H
hongming 已提交
27
	iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
28
	authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
H
hongming 已提交
29
	"kubesphere.io/kubesphere/pkg/apiserver/query"
H
update  
hongming 已提交
30
	"kubesphere.io/kubesphere/pkg/apiserver/request"
H
hongming 已提交
31
	"kubesphere.io/kubesphere/pkg/models/auth"
H
hongming 已提交
32
	"kubesphere.io/kubesphere/pkg/models/iam/im"
H
update  
hongming 已提交
33
	"net/http"
H
update  
hongming 已提交
34 35
)

H
hongming 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
const (
	KindTokenReview       = "TokenReview"
	passwordGrantType     = "password"
	refreshTokenGrantType = "refresh_token"
)

type Spec struct {
	Token string `json:"token" description:"access token"`
}

type Status struct {
	Authenticated bool                   `json:"authenticated" description:"is authenticated"`
	User          map[string]interface{} `json:"user,omitempty" description:"user info"`
}

type TokenReview struct {
	APIVersion string  `json:"apiVersion" description:"Kubernetes API version"`
	Kind       string  `json:"kind" description:"kind of the API object"`
	Spec       *Spec   `json:"spec,omitempty"`
	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 {
	if request.Spec == nil || request.Spec.Token == "" {
		return fmt.Errorf("token must not be null")
	}
	return nil
}

Z
zryfish 已提交
70
type handler struct {
H
hongming 已提交
71 72 73 74 75 76
	im                    im.IdentityManagementInterface
	options               *authoptions.AuthenticationOptions
	tokenOperator         auth.TokenManagementInterface
	passwordAuthenticator auth.PasswordAuthenticator
	oauth2Authenticator   auth.OAuth2Authenticator
	loginRecorder         auth.LoginRecorder
H
update  
hongming 已提交
77 78
}

H
hongming 已提交
79 80 81 82 83 84 85 86 87 88 89 90
func newHandler(im im.IdentityManagementInterface,
	tokenOperator auth.TokenManagementInterface,
	passwordAuthenticator auth.PasswordAuthenticator,
	oauth2Authenticator auth.OAuth2Authenticator,
	loginRecorder auth.LoginRecorder,
	options *authoptions.AuthenticationOptions) *handler {
	return &handler{im: im,
		tokenOperator:         tokenOperator,
		passwordAuthenticator: passwordAuthenticator,
		oauth2Authenticator:   oauth2Authenticator,
		loginRecorder:         loginRecorder,
		options:               options}
H
update  
hongming 已提交
91 92 93 94
}

// Implement webhook authentication interface
// https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
Z
zryfish 已提交
95
func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) {
H
hongming 已提交
96
	var tokenReview TokenReview
H
update  
hongming 已提交
97 98 99 100 101 102 103 104 105 106 107 108

	err := req.ReadEntity(&tokenReview)
	if err != nil {
		api.HandleBadRequest(resp, req, err)
		return
	}

	if err = tokenReview.Validate(); err != nil {
		api.HandleBadRequest(resp, req, err)
		return
	}

Z
zryfish 已提交
109
	authenticated, err := h.tokenOperator.Verify(tokenReview.Spec.Token)
H
update  
hongming 已提交
110 111 112 113 114
	if err != nil {
		api.HandleInternalError(resp, req, err)
		return
	}

H
hongming 已提交
115 116 117
	success := TokenReview{APIVersion: tokenReview.APIVersion,
		Kind: KindTokenReview,
		Status: &Status{
H
update  
hongming 已提交
118
			Authenticated: true,
Z
zryfish 已提交
119
			User:          map[string]interface{}{"username": authenticated.GetName(), "uid": authenticated.GetUID()},
H
update  
hongming 已提交
120 121 122 123 124
		},
	}

	resp.WriteEntity(success)
}
H
update  
hongming 已提交
125

Z
zryfish 已提交
126 127
func (h *handler) Authorize(req *restful.Request, resp *restful.Response) {
	authenticated, ok := request.UserFrom(req.Request.Context())
H
update  
hongming 已提交
128 129
	clientId := req.QueryParameter("client_id")
	responseType := req.QueryParameter("response_type")
130
	redirectURI := req.QueryParameter("redirect_uri")
H
update  
hongming 已提交
131

H
update  
hongming 已提交
132
	conf, err := h.options.OAuthOptions.OAuthClient(clientId)
H
update  
hongming 已提交
133 134
	if err != nil {
		err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
H
hongming 已提交
135
		api.HandleError(resp, req, err)
H
update  
hongming 已提交
136 137 138 139
		return
	}

	if responseType != "token" {
Z
zryfish 已提交
140
		err := apierrors.NewBadRequest(fmt.Sprintf("Response type %s is not supported", responseType))
H
hongming 已提交
141
		api.HandleError(resp, req, err)
H
update  
hongming 已提交
142 143 144 145 146
		return
	}

	if !ok {
		err := apierrors.NewUnauthorized("Unauthorized")
H
hongming 已提交
147
		api.HandleError(resp, req, err)
H
update  
hongming 已提交
148 149 150
		return
	}

Z
zryfish 已提交
151
	token, err := h.tokenOperator.IssueTo(authenticated)
152 153
	if err != nil {
		err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
H
hongming 已提交
154
		api.HandleError(resp, req, err)
155 156 157 158
		return
	}

	redirectURL, err := conf.ResolveRedirectURL(redirectURI)
H
update  
hongming 已提交
159 160
	if err != nil {
		err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
H
hongming 已提交
161
		api.HandleError(resp, req, err)
H
update  
hongming 已提交
162 163 164
		return
	}

H
hongming 已提交
165
	redirectURL = fmt.Sprintf("%s#access_token=%s&token_type=Bearer", redirectURL, token.AccessToken)
166

H
hongming 已提交
167 168
	if token.ExpiresIn > 0 {
		redirectURL = fmt.Sprintf("%s&expires_in=%v", redirectURL, token.ExpiresIn)
H
update  
hongming 已提交
169
	}
170
	resp.Header().Set("Content-Type", "text/plain")
H
update  
hongming 已提交
171 172
	http.Redirect(resp, req.Request, redirectURL, http.StatusFound)
}
173

H
hongming 已提交
174
func (h *handler) oauthCallBack(req *restful.Request, resp *restful.Response) {
175
	code := req.QueryParameter("code")
H
hongming 已提交
176
	provider := req.PathParameter("callback")
177 178 179

	if code == "" {
		err := apierrors.NewUnauthorized("Unauthorized: missing code")
H
hongming 已提交
180
		api.HandleError(resp, req, err)
H
hongming 已提交
181
		return
182 183
	}

H
hongming 已提交
184
	authenticated, provider, err := h.oauth2Authenticator.Authenticate(provider, code)
185
	if err != nil {
H
hongming 已提交
186
		api.HandleUnauthorized(resp, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
H
hongming 已提交
187 188 189
		return
	}

H
hongming 已提交
190
	result, err := h.tokenOperator.IssueTo(authenticated)
H
hongming 已提交
191
	if err != nil {
H
hongming 已提交
192
		api.HandleInternalError(resp, req, apierrors.NewInternalError(err))
H
hongming 已提交
193 194
		return
	}
Z
zryfish 已提交
195

H
hongming 已提交
196 197 198
	requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
	if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
		klog.Errorf("Failed to record successful login for user %s, error: %v", authenticated.GetName(), err)
Z
zryfish 已提交
199 200
	}

H
hongming 已提交
201 202 203
	resp.WriteEntity(result)
}

Z
zryfish 已提交
204
func (h *handler) Login(request *restful.Request, response *restful.Response) {
H
hongming 已提交
205
	var loginRequest LoginRequest
H
hongming 已提交
206
	err := request.ReadEntity(&loginRequest)
H
hongming 已提交
207 208
	if err != nil {
		api.HandleBadRequest(response, request, err)
H
hongming 已提交
209 210
		return
	}
Z
zryfish 已提交
211 212
	h.passwordGrant(loginRequest.Username, loginRequest.Password, request, response)
}
H
hongming 已提交
213

Z
zryfish 已提交
214 215
func (h *handler) Token(req *restful.Request, response *restful.Response) {
	grantType, err := req.BodyParameter("grant_type")
H
hongming 已提交
216
	if err != nil {
Z
zryfish 已提交
217
		api.HandleBadRequest(response, req, err)
H
hongming 已提交
218 219
		return
	}
Z
zryfish 已提交
220
	switch grantType {
H
hongming 已提交
221 222 223
	case passwordGrantType:
		username, _ := req.BodyParameter("username")
		password, _ := req.BodyParameter("password")
Z
zryfish 已提交
224 225
		h.passwordGrant(username, password, req, response)
		break
H
hongming 已提交
226
	case refreshTokenGrantType:
Z
zryfish 已提交
227 228 229 230
		h.refreshTokenGrant(req, response)
		break
	default:
		err := apierrors.NewBadRequest(fmt.Sprintf("Grant type %s is not supported", grantType))
H
hongming 已提交
231
		api.HandleBadRequest(response, req, err)
Z
zryfish 已提交
232 233
	}
}
H
hongming 已提交
234

Z
zryfish 已提交
235
func (h *handler) passwordGrant(username string, password string, req *restful.Request, response *restful.Response) {
H
hongming 已提交
236
	authenticated, provider, err := h.passwordAuthenticator.Authenticate(username, password)
H
hongming 已提交
237
	if err != nil {
238
		switch err {
H
hongming 已提交
239 240 241 242
		case auth.IncorrectPasswordError:
			requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
			if err := h.loginRecorder.RecordLogin(username, iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, err); err != nil {
				klog.Errorf("Failed to record unsuccessful login attempt for user %s, error: %v", username, err)
Z
zryfish 已提交
243
			}
H
hongming 已提交
244
			api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err)))
245
			return
H
hongming 已提交
246 247
		case auth.RateLimitExceededError:
			api.HandleTooManyRequests(response, req, apierrors.NewTooManyRequestsError(fmt.Sprintf("Unauthorized: %s", err)))
248 249
			return
		default:
H
hongming 已提交
250
			api.HandleInternalError(response, req, apierrors.NewInternalError(err))
H
hongming 已提交
251
			return
Z
zryfish 已提交
252
		}
H
hongming 已提交
253 254
	}

Z
zryfish 已提交
255 256
	result, err := h.tokenOperator.IssueTo(authenticated)
	if err != nil {
H
hongming 已提交
257
		api.HandleInternalError(response, req, apierrors.NewInternalError(err))
Z
zryfish 已提交
258 259 260
		return
	}

H
hongming 已提交
261 262 263
	requestInfo, _ := request.RequestInfoFrom(req.Request.Context())
	if err = h.loginRecorder.RecordLogin(authenticated.GetName(), iamv1alpha2.Token, provider, requestInfo.SourceIP, requestInfo.UserAgent, nil); err != nil {
		klog.Errorf("Failed to record successful login for user %s, error: %v", username, err)
Z
zryfish 已提交
264
	}
265

Z
zryfish 已提交
266 267
	response.WriteEntity(result)
}
268

Z
zryfish 已提交
269 270
func (h *handler) refreshTokenGrant(req *restful.Request, response *restful.Response) {
	refreshToken, err := req.BodyParameter("refresh_token")
271
	if err != nil {
H
hongming 已提交
272
		api.HandleBadRequest(response, req, apierrors.NewBadRequest(err.Error()))
Z
zryfish 已提交
273
		return
274 275
	}

Z
zryfish 已提交
276 277 278
	authenticated, err := h.tokenOperator.Verify(refreshToken)
	if err != nil {
		err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
H
hongming 已提交
279
		api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
Z
zryfish 已提交
280
		return
281 282
	}

H
hongming 已提交
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
	// update token after registration
	if authenticated.GetName() == iamv1alpha2.PreRegistrationUser &&
		authenticated.GetExtra() != nil &&
		len(authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider]) > 0 &&
		len(authenticated.GetExtra()[iamv1alpha2.ExtraUID]) > 0 {

		idp := authenticated.GetExtra()[iamv1alpha2.ExtraIdentityProvider][0]
		uid := authenticated.GetExtra()[iamv1alpha2.ExtraUID][0]
		queryParam := query.New()
		queryParam.LabelSelector = labels.SelectorFromSet(labels.Set{
			iamv1alpha2.IdentifyProviderLabel: idp,
			iamv1alpha2.OriginUIDLabel:        uid}).String()
		result, err := h.im.ListUsers(queryParam)
		if err != nil {
			api.HandleInternalError(response, req, apierrors.NewInternalError(err))
			return
		}
		if len(result.Items) != 1 {
			err := apierrors.NewUnauthorized("authenticated user does not exist")
			api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
			return
		}
		authenticated = &user.DefaultInfo{Name: result.Items[0].(*iamv1alpha2.User).Name}
	}

Z
zryfish 已提交
308 309 310
	result, err := h.tokenOperator.IssueTo(authenticated)
	if err != nil {
		err := apierrors.NewUnauthorized(fmt.Sprintf("Unauthorized: %s", err))
H
hongming 已提交
311
		api.HandleUnauthorized(response, req, apierrors.NewUnauthorized(err.Error()))
Z
zryfish 已提交
312 313 314 315
		return
	}

	response.WriteEntity(result)
316
}