| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- // Copyright 2019 Yunion
- //
- // 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 tokens
- import (
- "context"
- "net/http"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/sqlchemy"
- api "yunion.io/x/onecloud/pkg/apis/identity"
- "yunion.io/x/onecloud/pkg/appsrv"
- "yunion.io/x/onecloud/pkg/cloudcommon/policy"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/keystone/cache"
- "yunion.io/x/onecloud/pkg/keystone/models"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/mcclient/auth"
- "yunion.io/x/onecloud/pkg/util/netutils2"
- )
- func AddHandler(app *appsrv.Application) {
- app.AddHandler2("POST", "/v2.0/tokens", authenticateTokensV2, nil, "auth_tokens_v2", nil)
- app.AddHandler2("POST", "/v3/auth/tokens", authenticateTokensV3, nil, "auth_tokens_v3", nil)
- app.AddHandler2("GET", "/v2.0/tokens/<token>", authenticateToken(verifyTokensV2), nil, "verify_tokens_v2", nil)
- app.AddHandler2("GET", "/v3/auth/tokens", authenticateToken(verifyTokensV3), nil, "verify_tokens_v3", nil)
- app.AddHandler2("GET", "/v3/auth/policies", authenticateToken(fetchTokenPolicies), nil, "fetch_token_policies", nil)
- app.AddHandler2("POST", "/v3/auth/policies", authenticateToken(postTokenPolicies), nil, "post_token_policies", nil)
- app.AddHandler2("DELETE", "/v3/auth/tokens", authenticateToken(invalidateTokenV3), nil, "delete_tokens_v3", nil)
- app.AddHandler2("GET", "/v3/auth/tokens/invalid", authenticateToken(fetchInvalidTokensV3), nil, "fetch_revoked_tokens_v3", nil)
- }
- func FetchAuthContext(authCtx mcclient.SAuthContext, r *http.Request) mcclient.SAuthContext {
- if len(authCtx.Source) == 0 {
- authCtx.Source = mcclient.AuthSourceAPI
- }
- if len(authCtx.Ip) == 0 || authCtx.Ip == "0.0.0.0" {
- authCtx.Ip = netutils2.GetHttpRequestIp(r)
- }
- return authCtx
- }
- func authenticateTokensV2(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- _, _, body := appsrv.FetchEnv(ctx, w, r)
- input := mcclient.SAuthenticationInputV2{}
- err := body.Unmarshal(&input)
- if err != nil {
- httperrors.InvalidInputError(ctx, w, "unrecognized input %s", err)
- return
- }
- input.Auth.Context = FetchAuthContext(input.Auth.Context, r)
- token, err := AuthenticateV2(ctx, input)
- if err != nil {
- log.Errorf("AuthenticateV2 error %s", err)
- httperrors.GeneralServerError(ctx, w, err)
- return
- }
- if token == nil {
- httperrors.UnauthorizedError(ctx, w, "unauthorized %s", err)
- return
- }
- appsrv.SendJSON(w, jsonutils.Marshal(token))
- models.UserManager.TraceLoginV2(ctx, &token.Access)
- }
- func authenticateTokensV3(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- _, _, body := appsrv.FetchEnv(ctx, w, r)
- if body == nil {
- httperrors.InvalidInputError(ctx, w, "fail to decode request body")
- return
- }
- input := mcclient.SAuthenticationInputV3{}
- err := body.Unmarshal(&input)
- if err != nil {
- httperrors.InvalidInputError(ctx, w, "unrecognized input %s", err)
- return
- }
- input.Auth.Context = FetchAuthContext(input.Auth.Context, r)
- token, err := AuthenticateV3(ctx, input)
- if err != nil {
- log.Errorf("AuthenticateV3 error %s", err)
- switch errors.Cause(err) {
- case sqlchemy.ErrDuplicateEntry:
- httperrors.ConflictError(ctx, w, "duplicate username")
- case httperrors.ErrTooManyAttempts,
- httperrors.ErrUserNotFound,
- httperrors.ErrUserDisabled,
- httperrors.ErrUserLocked,
- httperrors.ErrInvalidIdpStatus,
- httperrors.ErrWrongPassword:
- httperrors.GeneralServerError(ctx, w, err)
- default:
- httperrors.UnauthorizedError(ctx, w, "unauthorized %s", err)
- }
- return
- }
- if token == nil {
- httperrors.UnauthorizedError(ctx, w, "user not found or not enabled")
- return
- }
- w.Header().Set(api.AUTH_SUBJECT_TOKEN_HEADER, token.Id)
- appsrv.SendJSON(w, jsonutils.Marshal(token))
- models.UserManager.TraceLoginV3(ctx, token)
- }
- // swagger:parameters verifyTokensV2
- type VerifyTokenV2Param struct {
- // keystone V2验证token
- // in:path
- // required:true
- Token string
- }
- // swagger:route GET /v2.0/tokens/{token} authentication verifyTokensV2
- //
- // keystone v2验证token API
- //
- // keystone v2验证token API
- //
- // Responses:
- // 200: tokens_AuthenticateV2Output
- func verifyTokensV2(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- params, _, _ := appsrv.FetchEnv(ctx, w, r)
- tokenStr := params["<token>"]
- cachedToken := cache.Get(tokenStr)
- if cachedToken != nil {
- if v2token, ok := cachedToken.(*mcclient.TokenCredentialV2); ok && v2token.IsValid() {
- ret := jsonutils.NewDict()
- ret.Add(jsonutils.Marshal(v2token), "access")
- appsrv.SendJSON(w, ret)
- return
- } else {
- cache.Remove(tokenStr)
- }
- }
- token, err := verifyCommon(ctx, w, tokenStr)
- if err != nil {
- httperrors.GeneralServerError(ctx, w, err)
- return
- }
- user, err := models.UserManager.FetchUserExtended(token.UserId, "", "", "")
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid user")
- return
- }
- project, err := models.ProjectManager.FetchProject(token.ProjectId, "", "", "")
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid project")
- return
- }
- projExt, err := project.FetchExtend()
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid project")
- return
- }
- v2token, err := token.getTokenV2(ctx, user, projExt)
- if err != nil {
- httperrors.InternalServerError(ctx, w, "internal server error %s", err)
- return
- }
- ret := jsonutils.NewDict()
- ret.Add(jsonutils.Marshal(v2token), "access")
- appsrv.SendJSON(w, ret)
- }
- // swagger:parameters verifyTokensV3
- type VerifyTokenV3Param struct {
- // keystone V3验证token
- // in:header
- // required:true
- Token string `json:"X-Subject-Token"`
- }
- // swagger:route GET /v3/auth/tokens authentication verifyTokensV3
- //
- // keystone v3验证token API
- //
- // keystone v3验证token API
- //
- // Responses:
- // 200: tokens_AuthenticateV3Output
- func verifyTokensV3(ctx context.Context, w http.ResponseWriter, r *http.Request) {
- tokenStr := r.Header.Get(api.AUTH_SUBJECT_TOKEN_HEADER)
- cachedToken := cache.Get(tokenStr)
- if cachedToken != nil {
- if v3token, ok := cachedToken.(*mcclient.TokenCredentialV3); ok && v3token.IsValid() {
- w.Header().Set(api.AUTH_SUBJECT_TOKEN_HEADER, v3token.Id)
- v3token.Id = ""
- appsrv.SendJSON(w, jsonutils.Marshal(v3token))
- return
- } else {
- cache.Remove(tokenStr)
- }
- }
- token, err := verifyCommon(ctx, w, tokenStr)
- if err != nil {
- httperrors.GeneralServerError(ctx, w, err)
- return
- }
- user, err := models.UserManager.FetchUserExtended(token.UserId, "", "", "")
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid user")
- return
- }
- var projExt *models.SProjectExtended
- var domain *models.SDomain
- if len(token.ProjectId) > 0 {
- project, err := models.ProjectManager.FetchProject(token.ProjectId, "", "", "")
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid project")
- return
- }
- projExt, err = project.FetchExtend()
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid project")
- return
- }
- } else if len(token.DomainId) > 0 {
- domain, err = models.DomainManager.FetchDomainById(token.DomainId)
- if err != nil {
- httperrors.InvalidCredentialError(ctx, w, "invalid domain")
- return
- }
- }
- v3token, err := token.getTokenV3(ctx, user, projExt, domain, api.SAccessKeySecretInfo{})
- if err != nil {
- httperrors.InternalServerError(ctx, w, "internal server error %s", err)
- return
- }
- w.Header().Set(api.AUTH_SUBJECT_TOKEN_HEADER, v3token.Id)
- v3token.Id = ""
- appsrv.SendJSON(w, jsonutils.Marshal(v3token))
- }
- func verifyCommon(ctx context.Context, w http.ResponseWriter, tokenStr string) (*SAuthToken, error) {
- adminToken := policy.FetchUserCredential(ctx)
- if adminToken == nil || len(tokenStr) == 0 {
- return nil, httperrors.NewForbiddenError("missing auth token")
- }
- result := policy.PolicyManager.Allow(rbacscope.ScopeSystem, adminToken, api.SERVICE_TYPE, "tokens", "perform", "auth")
- if result.Result.IsDeny() {
- return nil, httperrors.NewForbiddenError("%s not allow to auth", adminToken.GetUserName())
- }
- token, err := TokenStrDecode(ctx, tokenStr)
- if err != nil {
- return nil, httperrors.NewInvalidCredentialError("invalid token: %v", err)
- }
- return token, nil
- }
- func authenticateToken(f appsrv.FilterHandler) appsrv.FilterHandler {
- return authenticateTokenWithDelayDecision(f, true)
- }
- func authenticateTokenWithDelayDecision(f appsrv.FilterHandler, delayDecision bool) appsrv.FilterHandler {
- return auth.AuthenticateWithDelayDecision(f, delayDecision)
- }
|