mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-01 15:07:17 +08:00
1337 lines
40 KiB
Go
1337 lines
40 KiB
Go
// 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 handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/httputils"
|
|
"yunion.io/x/pkg/util/printutils"
|
|
"yunion.io/x/pkg/util/rbacscope"
|
|
|
|
"yunion.io/x/onecloud/pkg/apigateway/clientman"
|
|
"yunion.io/x/onecloud/pkg/apigateway/constants"
|
|
"yunion.io/x/onecloud/pkg/apigateway/options"
|
|
policytool "yunion.io/x/onecloud/pkg/apigateway/policy"
|
|
agapi "yunion.io/x/onecloud/pkg/apis/apigateway"
|
|
"yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/appsrv"
|
|
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
|
|
"yunion.io/x/onecloud/pkg/httperrors"
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
|
compute_modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
|
modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
|
"yunion.io/x/onecloud/pkg/mcclient/modules/notify"
|
|
"yunion.io/x/onecloud/pkg/util/logclient"
|
|
"yunion.io/x/onecloud/pkg/util/netutils2"
|
|
"yunion.io/x/onecloud/pkg/util/seclib2"
|
|
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
|
)
|
|
|
|
func AppContextToken(ctx context.Context) mcclient.TokenCredential {
|
|
return auth.FetchUserCredential(ctx, nil)
|
|
}
|
|
|
|
type AuthHandlers struct {
|
|
*SHandlers
|
|
preLoginHook PreLoginFunc
|
|
}
|
|
|
|
func NewAuthHandlers(prefix string, preLoginHook PreLoginFunc) *AuthHandlers {
|
|
return &AuthHandlers{
|
|
SHandlers: NewHandlers(prefix),
|
|
preLoginHook: preLoginHook,
|
|
}
|
|
}
|
|
|
|
func (h *AuthHandlers) AddMethods() {
|
|
// no middleware handler
|
|
h.AddByMethod(GET, nil,
|
|
NewHP(h.getRegions, "regions"),
|
|
NewHP(h.getIdpSsoRedirectUri, "sso", "redirect", "<idp_id>"),
|
|
NewHP(h.listTotpRecoveryQuestions, "recovery"),
|
|
NewHP(h.handleSsoLogin, "ssologin"),
|
|
NewHP(h.handleIdpInitSsoLogin, "ssologin", "<idp_id>"),
|
|
NewHP(h.postLogoutHandler, "logout"),
|
|
// oidc auth
|
|
NewHP(handleOIDCAuth, "oidc", "auth"),
|
|
NewHP(handleOIDCConfiguration, "oidc", ".well-known", "openid-configuration"),
|
|
NewHP(handleOIDCJWKeys, "oidc", "keys"),
|
|
NewHP(handleOIDCUserInfo, "oidc", "user"),
|
|
NewHP(handleOIDCRPInitLogout, "oidc", "logout"),
|
|
)
|
|
h.AddByMethod(POST, nil,
|
|
NewHP(h.initTotpSecrets, "initcredential"),
|
|
NewHP(h.resetTotpSecrets, "credential"),
|
|
NewHP(h.validatePasscode, "passcode"),
|
|
NewHP(h.resetTotpRecoveryQuestions, "recovery"),
|
|
NewHP(h.postLoginHandler, "login"),
|
|
NewHP(h.postLogoutHandler, "logout"),
|
|
NewHP(h.handleSsoLogin, "ssologin"),
|
|
NewHP(h.handleIdpInitSsoLogin, "ssologin", "<idp_id>"),
|
|
NewHP(handleOIDCToken, "oidc", "token"),
|
|
NewHP(handleOIDCRPInitLogout, "oidc", "logout"),
|
|
)
|
|
|
|
// auth middleware handler
|
|
h.AddByMethod(GET, FetchAuthToken,
|
|
NewHP(h.getUser, "user"),
|
|
NewHP(h.getStats, "stats"),
|
|
NewHP(h.getUserSubscription, "subscriptions"),
|
|
NewHP(h.getPermissionDetails, "permissions"),
|
|
NewHP(h.getAdminResources, "admin_resources"),
|
|
NewHP(h.getResources, "scoped_resources"),
|
|
NewHP(fetchIdpBasicConfig, "idp", "<idp_id>", "info"),
|
|
NewHP(fetchIdpSAMLMetadata, "idp", "<idp_id>", "saml-metadata"),
|
|
)
|
|
h.AddByMethod(POST, FetchAuthToken,
|
|
NewHP(h.resetUserPassword, "password"),
|
|
NewHP(h.getPermissionDetails, "permissions"),
|
|
NewHP(h.doCreatePolicies, "policies"),
|
|
NewHP(handleUnlinkIdp, "unlink-idp"),
|
|
)
|
|
h.AddByMethod(PATCH, FetchAuthToken,
|
|
NewHP(h.doPatchPolicy, "policies", "<policy_id>"),
|
|
)
|
|
h.AddByMethod(DELETE, FetchAuthToken,
|
|
NewHP(h.doDeletePolicies, "policies"),
|
|
)
|
|
}
|
|
|
|
func (h *AuthHandlers) Bind(app *appsrv.Application) {
|
|
h.AddMethods()
|
|
h.SHandlers.Bind(app)
|
|
}
|
|
|
|
func (h *AuthHandlers) GetRegionsResponse(ctx context.Context, w http.ResponseWriter, req *http.Request) (*agapi.SRegionsReponse, error) {
|
|
var currentDomain string
|
|
var createUser bool
|
|
qs, _ := jsonutils.ParseQueryString(req.URL.RawQuery)
|
|
if qs != nil {
|
|
currentDomain, _ = qs.GetString("domain")
|
|
createUser = jsonutils.QueryBoolean(qs, "auto_create_user", true)
|
|
}
|
|
|
|
adminToken := auth.AdminCredential()
|
|
if adminToken == nil {
|
|
return nil, errors.Error("failed to get admin credential")
|
|
}
|
|
regions := adminToken.GetRegions()
|
|
if len(regions) == 0 {
|
|
return nil, errors.Error("region is empty")
|
|
}
|
|
|
|
ret := &agapi.SRegionsReponse{
|
|
Regions: regions,
|
|
Domains: []string{},
|
|
ReturnFullDomains: false,
|
|
Idps: []agapi.SIdp{},
|
|
EncryptPasswd: true,
|
|
ApiServer: options.Options.ApiServer,
|
|
}
|
|
|
|
s := auth.GetAdminSession(ctx, regions[0])
|
|
if options.Options.ReturnFullDomainList {
|
|
filters := jsonutils.NewDict()
|
|
if len(currentDomain) > 0 {
|
|
filters.Add(jsonutils.NewString(currentDomain), "name")
|
|
}
|
|
filters.Add(jsonutils.NewInt(1000), "limit")
|
|
result, e := modules.Domains.List(s, filters)
|
|
if e != nil {
|
|
return nil, errors.Wrap(e, "list domain")
|
|
}
|
|
domains := []struct {
|
|
Name string
|
|
Enabled bool
|
|
}{}
|
|
jsonutils.Update(&domains, result.Data)
|
|
for _, d := range domains {
|
|
if len(d.Name) > 0 && d.Enabled {
|
|
ret.Domains = append(ret.Domains, d.Name)
|
|
}
|
|
}
|
|
ret.ReturnFullDomains = true
|
|
}
|
|
|
|
filters := jsonutils.NewDict()
|
|
filters.Add(jsonutils.JSONTrue, "enabled")
|
|
if len(currentDomain) == 0 {
|
|
currentDomain = "all"
|
|
}
|
|
filters.Add(jsonutils.NewString(currentDomain), "sso_domain")
|
|
filters.Add(jsonutils.NewString("system"), "scope")
|
|
filters.Add(jsonutils.NewInt(1000), "limit")
|
|
if !createUser {
|
|
filters.Add(jsonutils.JSONFalse, "auto_create_user")
|
|
}
|
|
idps, err := modules.IdentityProviders.List(s, filters)
|
|
if err == nil {
|
|
jsonutils.Update(&ret.Idps, idps.Data)
|
|
}
|
|
|
|
// in case api_server changed but not synchronized from Keystone
|
|
// fetch this option directly from Keystone
|
|
commonCfg, err := modules.ServicesV3.GetSpecific(s, "common", "config", nil)
|
|
if err == nil && commonCfg != nil {
|
|
apiServer, _ := commonCfg.GetString("config", "default", "api_server")
|
|
if len(apiServer) > 0 {
|
|
ret.ApiServer = apiServer
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (h *AuthHandlers) getRegions(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
resp, err := h.GetRegionsResponse(ctx, w, req)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
appsrv.SendJSON(w, jsonutils.Marshal(resp))
|
|
}
|
|
|
|
func (h *AuthHandlers) getUser(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
data, err := getUserInfo(ctx, req)
|
|
if err != nil {
|
|
httperrors.NotFoundError(ctx, w, err.Error())
|
|
return
|
|
}
|
|
body := jsonutils.NewDict()
|
|
body.Add(data, "data")
|
|
|
|
appsrv.SendJSON(w, body)
|
|
}
|
|
|
|
func (h *AuthHandlers) getUserSubscription(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
token := AppContextToken(ctx)
|
|
result, err := notify.NotifyReceiver.PerformAction(s, token.GetUserId(), "get-subscription", jsonutils.Marshal(map[string]interface{}{
|
|
"receiver": map[string]string{
|
|
"id": token.GetUserId(),
|
|
},
|
|
}))
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
appsrv.SendJSON(w, result)
|
|
}
|
|
|
|
func getStatsInfo(ctx context.Context, req *http.Request) (jsonutils.JSONObject, error) {
|
|
token := AppContextToken(ctx)
|
|
s := auth.GetSession(ctx, token, FetchRegion(req))
|
|
params, _ := jsonutils.ParseQueryString(req.URL.RawQuery)
|
|
|
|
if params == nil {
|
|
params = jsonutils.NewDict()
|
|
}
|
|
params.(*jsonutils.JSONDict).Add(jsonutils.NewInt(1), "limit")
|
|
|
|
ret := struct {
|
|
Cloudaccounts int
|
|
}{}
|
|
|
|
accounts, err := compute_modules.Cloudaccounts.List(s, params)
|
|
if err != nil && httputils.ErrorCode(err) != 403 {
|
|
return nil, err
|
|
}
|
|
|
|
if accounts != nil {
|
|
ret.Cloudaccounts = accounts.Total
|
|
}
|
|
|
|
return jsonutils.Marshal(ret), nil
|
|
}
|
|
|
|
func (h *AuthHandlers) getStats(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
data, err := getStatsInfo(ctx, req)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
body := jsonutils.NewDict()
|
|
body.Add(data, "data")
|
|
|
|
appsrv.SendJSON(w, body)
|
|
}
|
|
|
|
func (h *AuthHandlers) initTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
initTotpSecrets(ctx, w, req)
|
|
}
|
|
|
|
func (h *AuthHandlers) resetTotpSecrets(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
resetTotpSecrets(ctx, w, req)
|
|
}
|
|
|
|
func (h *AuthHandlers) validatePasscode(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
validatePasscodeHandler(ctx, w, req)
|
|
}
|
|
|
|
func (h *AuthHandlers) resetTotpRecoveryQuestions(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
resetTotpRecoveryQuestions(ctx, w, req)
|
|
}
|
|
|
|
func (h *AuthHandlers) listTotpRecoveryQuestions(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
listTotpRecoveryQuestions(ctx, w, req)
|
|
}
|
|
|
|
// 返回 token及totp验证状态
|
|
func doTenantLogin(ctx context.Context, req *http.Request, body jsonutils.JSONObject) (mcclient.TokenCredential, *clientman.SAuthToken, error) {
|
|
tenantId, err := body.GetString("tenantId")
|
|
if err != nil {
|
|
return nil, nil, httperrors.NewInputParameterError("not found tenantId in body")
|
|
}
|
|
token, authToken, err := fetchAuthInfo(ctx, req)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(httperrors.ErrInvalidCredential, "fetchAuthToken fail %s", err)
|
|
}
|
|
if !authToken.IsTotpVerified() {
|
|
return nil, nil, errors.Wrap(httperrors.ErrInvalidCredential, "TOTP authentication failed")
|
|
}
|
|
|
|
ntoken, err := auth.Client().SetProject(tenantId, "", "", token)
|
|
if err != nil {
|
|
return nil, nil, httperrors.NewInvalidCredentialError("failed to change project")
|
|
}
|
|
|
|
authToken.SetToken(ntoken.GetTokenString())
|
|
return ntoken, authToken, nil
|
|
}
|
|
|
|
func fetchUserInfoFromToken(ctx context.Context, req *http.Request, token mcclient.TokenCredential) (jsonutils.JSONObject, error) {
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
return fetchUserInfoById(s, token.GetUserId())
|
|
}
|
|
|
|
func fetchUserInfoById(s *mcclient.ClientSession, userId string) (jsonutils.JSONObject, error) {
|
|
info, err := modules.UsersV3.Get(s, userId, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "UsersV3.Get")
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
func isUserEnableTotp(userInfo jsonutils.JSONObject) bool {
|
|
return jsonutils.QueryBoolean(userInfo, "enable_mfa", false)
|
|
}
|
|
|
|
func (h *AuthHandlers) doCredentialLogin(ctx context.Context, req *http.Request, body jsonutils.JSONObject) (mcclient.TokenCredential, error) {
|
|
var token mcclient.TokenCredential
|
|
var err error
|
|
var tenant string
|
|
// log.Debugf("doCredentialLogin body: %s", body)
|
|
cliIp := netutils2.GetHttpRequestIp(req)
|
|
if body.Contains("username") {
|
|
if h.preLoginHook != nil {
|
|
if err = h.preLoginHook(ctx, req, body); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
uname, _ := body.GetString("username")
|
|
var passwd string
|
|
passwd, err = body.GetString("password")
|
|
if err != nil {
|
|
return nil, httperrors.NewInputParameterError("get password in body")
|
|
}
|
|
passwd = decodePassword(passwd)
|
|
|
|
if len(uname) == 0 || len(passwd) == 0 {
|
|
return nil, httperrors.NewInputParameterError("username or password is empty")
|
|
}
|
|
|
|
tenant, uname = parseLoginUser(uname)
|
|
// var token mcclient.TokenCredential
|
|
domain, _ := body.GetString("domain")
|
|
token, err = auth.Client().AuthenticateWeb(uname, passwd, domain, "", "", cliIp)
|
|
} else if body.Contains("idp_driver") { // sso login
|
|
idpId, _ := body.GetString("idp_id")
|
|
redirectUri := getSsoCallbackUrl(ctx, req, idpId)
|
|
token, err = processSsoLoginData(body, cliIp, redirectUri)
|
|
// if err != nil {
|
|
// return nil, errors.Wrap(err, "processSsoLoginData")
|
|
// }
|
|
} else if body.Contains("verify_code") { // verify by mobile
|
|
if h.preLoginHook != nil {
|
|
if err = h.preLoginHook(ctx, req, body); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
verifyCode, _ := body.GetString("verify_code")
|
|
uid, _ := body.GetString("uid")
|
|
contactType, _ := body.GetString("contact_type")
|
|
token, err = processVerifyLoginData(uid, contactType, verifyCode, cliIp)
|
|
} else {
|
|
return nil, httperrors.NewInputParameterError("missing credential")
|
|
}
|
|
if err != nil {
|
|
switch httperr := err.(type) {
|
|
case *httputils.JSONClientError:
|
|
if httperr.Code >= 500 {
|
|
return nil, err
|
|
}
|
|
if httperr.Code == 409 || httperr.Code == 429 {
|
|
return nil, err
|
|
}
|
|
switch errors.Error(httperr.Class) {
|
|
case httperrors.ErrUserNotFound, httperrors.ErrWrongPassword:
|
|
return nil, httperrors.NewJsonClientError(httperrors.ErrIncorrectUsernameOrPassword, "incorrect username or password")
|
|
case httperrors.ErrUserLocked:
|
|
return nil, httperrors.NewJsonClientError(httperrors.ErrUserLocked, "The user has been locked, please contact the administrator")
|
|
case httperrors.ErrUserDisabled:
|
|
return nil, httperrors.NewJsonClientError(httperrors.ErrUserDisabled, "The user has been disabled, please contact the administrator")
|
|
case httperrors.ErrInvalidIdpStatus:
|
|
return nil, httperrors.NewJsonClientError(httperrors.ErrInvalidIdpStatus, "The IDP of user has been disabled or in invalid status")
|
|
}
|
|
}
|
|
return nil, httperrors.NewInvalidCredentialError("invalid credential")
|
|
}
|
|
uname := token.GetUserName()
|
|
if len(tenant) > 0 {
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
jsonProj, e := modules.Projects.GetById(s, tenant, nil)
|
|
if e != nil {
|
|
log.Errorf("fail to find preset project %s, reset to empty", tenant)
|
|
tenant = ""
|
|
} else {
|
|
projId, _ := jsonProj.GetString("id")
|
|
// projName, _ := jsonProj.GetString("name")
|
|
ntoken, e := auth.Client().SetProject(projId, "", "", token)
|
|
if e != nil {
|
|
log.Errorf("fail to change to preset project %s(%s), reset to empty", tenant, e)
|
|
tenant = ""
|
|
} else {
|
|
token = ntoken
|
|
}
|
|
}
|
|
}
|
|
if len(tenant) == 0 {
|
|
token3, ok := token.(*mcclient.TokenCredentialV3)
|
|
if ok {
|
|
targetLevel := rbacscope.ScopeProject
|
|
targetProjId := ""
|
|
for _, r := range token3.Token.RoleAssignments {
|
|
level := rbacscope.ScopeProject
|
|
if len(r.Policies.System) > 0 {
|
|
level = rbacscope.ScopeSystem
|
|
} else if len(r.Policies.Domain) > 0 {
|
|
level = rbacscope.ScopeDomain
|
|
}
|
|
if len(targetProjId) == 0 || level.HigherThan(targetLevel) {
|
|
targetProjId = r.Scope.Project.Id
|
|
targetLevel = level
|
|
}
|
|
}
|
|
if len(targetProjId) > 0 {
|
|
ntoken, e := auth.Client().SetProject(targetProjId, "", "", token)
|
|
if e != nil {
|
|
log.Errorf("fail to change to project %s(%s), reset to empty", targetProjId, e)
|
|
} else {
|
|
token = ntoken
|
|
tenant = targetProjId
|
|
body.(*jsonutils.JSONDict).Set("scope", jsonutils.NewString(string(targetLevel)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(tenant) == 0 {
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
projects, e := modules.UsersV3.GetProjects(s, token.GetUserId())
|
|
if e == nil && len(projects.Data) > 0 {
|
|
projectJson := projects.Data[0]
|
|
for _, pJson := range projects.Data {
|
|
pname, _ := pJson.GetString("name")
|
|
if pname == uname {
|
|
projectJson = pJson
|
|
break
|
|
}
|
|
}
|
|
pid, e := projectJson.GetString("id")
|
|
if e == nil {
|
|
ntoken, e := auth.Client().SetProject(pid, "", "", token)
|
|
if e == nil {
|
|
token = ntoken
|
|
} else {
|
|
log.Errorf("fail to change to default project %s(%s), reset to empty", pid, e)
|
|
}
|
|
}
|
|
} else {
|
|
log.Errorf("GetProjects for login user error %s project count %d", e, len(projects.Data))
|
|
}
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
func parseLoginUser(uname string) (string, string) {
|
|
slashpos := strings.IndexByte(uname, '/')
|
|
tenant := ""
|
|
if slashpos > 0 {
|
|
tenant = uname[0:slashpos]
|
|
uname = uname[slashpos+1:]
|
|
}
|
|
|
|
return tenant, uname
|
|
}
|
|
|
|
func isUserAllowWebconsole(userInfo jsonutils.JSONObject) bool {
|
|
return jsonutils.QueryBoolean(userInfo, "allow_web_console", true)
|
|
}
|
|
|
|
func saveCookie(w http.ResponseWriter, name, val, domain string, expire time.Time, base64 bool) {
|
|
var maxAge int
|
|
if !expire.IsZero() {
|
|
diff := time.Until(expire)
|
|
maxAge = int(diff.Seconds())
|
|
}
|
|
// log.Println("Set cookie", name, expire, maxAge, val)
|
|
var valenc string
|
|
if base64 {
|
|
valenc = Base64UrlEncode([]byte(val))
|
|
} else {
|
|
valenc = val
|
|
}
|
|
// log.Printf("Set coookie: %s - %s\n", val, valenc)
|
|
cookie := &http.Cookie{Name: name, Value: valenc, Path: "/", Expires: expire, MaxAge: maxAge, HttpOnly: false}
|
|
|
|
if len(domain) > 0 {
|
|
cookie.Domain = domain
|
|
}
|
|
|
|
http.SetCookie(w, cookie)
|
|
}
|
|
|
|
func getCookie(r *http.Request, name string) string {
|
|
return getCookie2(r, name, true)
|
|
}
|
|
|
|
func getCookie2(r *http.Request, name string, base64 bool) string {
|
|
cookie, err := r.Cookie(name)
|
|
if err != nil {
|
|
log.Errorf("Cookie not found %q", name)
|
|
return ""
|
|
// } else if cookie.Expires.Before(time.Now()) {
|
|
// fmt.Println("Cookie expired ", cookie.Expires, time.Now())
|
|
// return ""
|
|
} else {
|
|
if !base64 {
|
|
return cookie.Value
|
|
}
|
|
val, err := Base64UrlDecode(cookie.Value)
|
|
if err != nil {
|
|
log.Errorf("Cookie %q fail to decode: %v", name, err)
|
|
return ""
|
|
}
|
|
return string(val)
|
|
}
|
|
}
|
|
|
|
func clearCookie(w http.ResponseWriter, name string, domain string) {
|
|
cookie := &http.Cookie{Name: name, Expires: time.Now(), Path: "/", MaxAge: -1, HttpOnly: false}
|
|
if len(domain) > 0 {
|
|
cookie.Domain = domain
|
|
}
|
|
|
|
http.SetCookie(w, cookie)
|
|
}
|
|
|
|
func saveAuthCookie(w http.ResponseWriter, authToken *clientman.SAuthToken, token mcclient.TokenCredential) {
|
|
authCookie := authToken.GetAuthCookie(token)
|
|
expire := time.Time{}
|
|
if !options.Options.SessionLevelAuthCookie {
|
|
expire = token.GetExpires()
|
|
}
|
|
saveCookie(w, constants.YUNION_AUTH_COOKIE, authCookie, options.Options.CookieDomain, expire, true)
|
|
}
|
|
|
|
func clearAuthCookie(w http.ResponseWriter) {
|
|
clearCookie(w, constants.YUNION_AUTH_COOKIE, options.Options.CookieDomain)
|
|
}
|
|
|
|
type PreLoginFunc func(ctx context.Context, req *http.Request, body jsonutils.JSONObject) error
|
|
|
|
func (h *AuthHandlers) postLoginHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
body, err := appsrv.FetchJSON(req)
|
|
if err != nil {
|
|
httperrors.InvalidInputError(ctx, w, "fetch json for request: %v", err)
|
|
return
|
|
}
|
|
err = h.doLogin(ctx, w, req, body)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
// normal
|
|
appsrv.Send(w, "")
|
|
}
|
|
|
|
func (h *AuthHandlers) doLogin(ctx context.Context, w http.ResponseWriter, req *http.Request, body jsonutils.JSONObject) error {
|
|
var err error
|
|
var authToken *clientman.SAuthToken
|
|
var token mcclient.TokenCredential
|
|
var userInfo jsonutils.JSONObject
|
|
if body.Contains("tenantId") { // switch project
|
|
token, authToken, err = doTenantLogin(ctx, req, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userInfo, err = fetchUserInfoFromToken(ctx, req, token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// user/password authenticate
|
|
// SSO authentication
|
|
token, err = h.doCredentialLogin(ctx, req, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userInfo, err = fetchUserInfoFromToken(ctx, req, token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
isTotpInit, err := isUserTotpCredInitialed(s, token.GetUserId())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
isIdpLogin := body.Contains("idp_driver")
|
|
authToken = clientman.NewAuthToken(token.GetTokenString(), isUserEnableTotp(userInfo), isTotpInit, isIdpLogin)
|
|
}
|
|
|
|
if !isUserAllowWebconsole(userInfo) {
|
|
return httperrors.NewForbiddenError("user forbidden login from web")
|
|
}
|
|
|
|
saveAuthCookie(w, authToken, token)
|
|
|
|
if len(token.GetProjectId()) > 0 {
|
|
if body.Contains("isadmin") {
|
|
adminVal := "false"
|
|
if policy.PolicyManager.IsScopeCapable(token, rbacscope.ScopeSystem) {
|
|
adminVal, _ = body.GetString("isadmin")
|
|
}
|
|
saveCookie(w, "isadmin", adminVal, "", token.GetExpires(), false)
|
|
}
|
|
if body.Contains("scope") {
|
|
scopeStr, _ := body.GetString("scope")
|
|
if !policy.PolicyManager.IsScopeCapable(token, rbacscope.TRbacScope(scopeStr)) {
|
|
scopeStr = string(rbacscope.ScopeProject)
|
|
}
|
|
saveCookie(w, "scope", scopeStr, "", token.GetExpires(), false)
|
|
}
|
|
if body.Contains("domain") {
|
|
domainStr, _ := body.GetString("domain")
|
|
saveCookie(w, "domain", domainStr, "", token.GetExpires(), false)
|
|
}
|
|
saveCookie(w, "tenant", token.GetProjectId(), "", token.GetExpires(), false)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func doLogout(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
token, _, _ := fetchAuthInfo(ctx, req)
|
|
if token != nil {
|
|
// valid login, log the event
|
|
err := auth.Remove(ctx, token.GetTokenString())
|
|
if err != nil {
|
|
log.Errorf("remove token fail %s", err)
|
|
return
|
|
} else {
|
|
user := logclient.NewSimpleObject(token.GetUserId(), token.GetUserName(), "user")
|
|
logclient.AddActionLogWithContext(ctx, user, logclient.ACT_LOGOUT, "", token, true)
|
|
}
|
|
}
|
|
clearAuthCookie(w)
|
|
appsrv.DisableClientCache(w)
|
|
}
|
|
|
|
func (h *AuthHandlers) postLogoutHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
doLogout(ctx, w, req)
|
|
appsrv.Send(w, "")
|
|
}
|
|
|
|
func FetchRegion(req *http.Request) string {
|
|
r, e := req.Cookie("region")
|
|
if e == nil && len(r.Value) > 0 {
|
|
return r.Value
|
|
}
|
|
if len(options.Options.DefaultRegion) > 0 {
|
|
return options.Options.DefaultRegion
|
|
}
|
|
adminToken := auth.AdminCredential()
|
|
if adminToken == nil {
|
|
log.Errorf("FetchRegion: nil adminTken")
|
|
return ""
|
|
}
|
|
regions := adminToken.GetRegions()
|
|
if len(regions) == 0 {
|
|
log.Errorf("FetchRegion: empty region list")
|
|
return ""
|
|
}
|
|
for _, r := range regions {
|
|
if len(r) > 0 {
|
|
return r
|
|
}
|
|
}
|
|
log.Errorf("FetchRegion: no valid region")
|
|
return ""
|
|
}
|
|
|
|
type role struct {
|
|
id string
|
|
name string
|
|
}
|
|
|
|
type projectRoles struct {
|
|
id string
|
|
name string
|
|
domain string
|
|
domainId string
|
|
roles []role
|
|
}
|
|
|
|
func newProjectRoles(projectId, projectName, roleId, roleName string, domainId, domainName string) *projectRoles {
|
|
return &projectRoles{
|
|
id: projectId,
|
|
name: projectName,
|
|
domainId: domainId,
|
|
domain: domainName,
|
|
roles: []role{{id: roleId, name: roleName}},
|
|
}
|
|
}
|
|
|
|
func (this *projectRoles) add(roleId, roleName string) {
|
|
this.roles = append(this.roles, role{id: roleId, name: roleName})
|
|
}
|
|
|
|
func (this *projectRoles) getToken(user, userId, domain, domainId string, ip string) mcclient.TokenCredential {
|
|
return &mcclient.SSimpleToken{
|
|
Token: "faketoken",
|
|
Domain: domain,
|
|
DomainId: domainId,
|
|
User: user,
|
|
UserId: userId,
|
|
Project: this.name,
|
|
ProjectId: this.id,
|
|
ProjectDomain: this.domain,
|
|
ProjectDomainId: this.domainId,
|
|
Roles: strings.Join(this.getRoles(), ","),
|
|
RoleIds: strings.Join(this.getRoleIds(), ","),
|
|
Context: mcclient.SAuthContext{
|
|
Ip: ip,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (this *projectRoles) getRoles() []string {
|
|
roles := make([]string, 0)
|
|
for _, r := range this.roles {
|
|
roles = append(roles, r.name)
|
|
}
|
|
return roles
|
|
}
|
|
|
|
func (this *projectRoles) getRoleIds() []string {
|
|
roles := make([]string, 0)
|
|
for _, r := range this.roles {
|
|
roles = append(roles, r.id)
|
|
}
|
|
return roles
|
|
}
|
|
|
|
func (this *projectRoles) json(s *mcclient.ClientSession, user, userId, domain, domainId string, ip string) (jsonutils.JSONObject, map[string][]string) {
|
|
obj := jsonutils.NewDict()
|
|
obj.Add(jsonutils.NewString(this.id), "id")
|
|
obj.Add(jsonutils.NewString(this.name), "name")
|
|
obj.Add(jsonutils.NewString(this.domain), "domain")
|
|
obj.Add(jsonutils.NewString(this.domainId), "domain_id")
|
|
roleIds := make([]string, 0)
|
|
roles := jsonutils.NewArray()
|
|
for _, r := range this.roles {
|
|
role := jsonutils.NewDict()
|
|
role.Add(jsonutils.NewString(r.id), "id")
|
|
role.Add(jsonutils.NewString(r.name), "name")
|
|
roles.Add(role)
|
|
roleIds = append(roleIds, r.id)
|
|
}
|
|
obj.Add(roles, "roles")
|
|
|
|
policies, _ := modules.RolePolicies.FetchMatchedPolicies(s, roleIds, this.id, ip)
|
|
for _, scope := range []rbacscope.TRbacScope{
|
|
rbacscope.ScopeProject,
|
|
rbacscope.ScopeDomain,
|
|
rbacscope.ScopeSystem,
|
|
} {
|
|
if matches, ok := policies[string(scope)]; ok {
|
|
obj.Add(jsonutils.NewStringArray(matches), fmt.Sprintf("%s_policies", scope))
|
|
if len(matches) > 0 {
|
|
obj.Add(jsonutils.JSONTrue, fmt.Sprintf("%s_capable", scope))
|
|
} else {
|
|
obj.Add(jsonutils.JSONFalse, fmt.Sprintf("%s_capable", scope))
|
|
}
|
|
// backward compatible
|
|
if scope == rbacscope.ScopeSystem {
|
|
if len(matches) > 0 {
|
|
obj.Add(jsonutils.JSONTrue, "admin_capable")
|
|
} else {
|
|
obj.Add(jsonutils.JSONFalse, "admin_capable")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return obj, policies
|
|
}
|
|
|
|
func isLBAgentExists(s *mcclient.ClientSession) (bool, error) {
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.NewString("hb_last_seen.isnotempty()"), "filter.0")
|
|
params.Add(jsonutils.NewInt(1), "limit")
|
|
params.Add(jsonutils.JSONFalse, "details")
|
|
agents, err := compute_modules.LoadbalancerAgents.List(s, params)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "modules.LoadbalancerAgents.List")
|
|
}
|
|
|
|
if len(agents.Data) > 0 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func isBaremetalAgentExists(s *mcclient.ClientSession) (bool, error) {
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.NewString("agent_type.equals(baremetal)"), "filter.0")
|
|
params.Add(jsonutils.NewInt(1), "limit")
|
|
params.Add(jsonutils.JSONFalse, "details")
|
|
agents, err := compute_modules.Baremetalagents.List(s, params)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "modules.Baremetalagents.List")
|
|
}
|
|
|
|
if len(agents.Data) > 0 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func isEsxiAgentExists(s *mcclient.ClientSession) (bool, error) {
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.NewString("agent_type.equals(esxiagent)"), "filter.0")
|
|
params.Add(jsonutils.NewInt(1), "limit")
|
|
params.Add(jsonutils.JSONFalse, "details")
|
|
agents, err := compute_modules.Baremetalagents.List(s, params)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "modules.Baremetalagents.List esxiagent")
|
|
}
|
|
|
|
if len(agents.Data) > 0 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func isHostAgentExists(s *mcclient.ClientSession) (bool, error) {
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.JSONFalse, "show_emulated")
|
|
params.Add(jsonutils.JSONFalse, "baremetal")
|
|
params.Add(jsonutils.NewString("system"), "scope")
|
|
params.Add(jsonutils.NewInt(1), "limit")
|
|
params.Add(jsonutils.JSONFalse, "details")
|
|
agents, err := compute_modules.Hosts.List(s, params)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "modules.LoadbalancerAgents.List")
|
|
}
|
|
|
|
if len(agents.Data) > 0 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func getUserInfo(ctx context.Context, req *http.Request) (*jsonutils.JSONDict, error) {
|
|
token := AppContextToken(ctx)
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
/*log.Infof("getUserInfo modules.UsersV3.Get")
|
|
usr, err := modules.UsersV3.Get(s, token.GetUserId(), nil)
|
|
if err != nil {
|
|
log.Errorf("modules.UsersV3.Get fail %s", err)
|
|
return nil, fmt.Errorf("not found user %s", token.GetUserId())
|
|
}*/
|
|
// usr, err := fetchUserInfoFromToken(ctx, req, token)
|
|
return getUserInfo2(s, token.GetUserId(), token.GetProjectId(), token.GetLoginIp())
|
|
}
|
|
|
|
func getUserInfo2(s *mcclient.ClientSession, uid string, pid string, loginIp string) (*jsonutils.JSONDict, error) {
|
|
usr, err := fetchUserInfoById(s, uid)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "fetchUserInfoFromToken %s", uid)
|
|
}
|
|
data := jsonutils.NewDict()
|
|
for _, k := range []string{
|
|
"displayname", "email", "id", "name",
|
|
"enabled", "mobile", "allow_web_console",
|
|
"created_at", "enable_mfa", "is_system_account",
|
|
"last_active_at", "last_login_ip",
|
|
"last_login_source",
|
|
"password_expires_at", "failed_auth_count", "failed_auth_at",
|
|
"need_reset_password", "password_reset_hint",
|
|
"idps",
|
|
"is_local",
|
|
} {
|
|
v, e := usr.Get(k)
|
|
if e == nil {
|
|
data.Add(v, k)
|
|
}
|
|
}
|
|
usrId, _ := usr.GetString("id")
|
|
usrName, _ := usr.GetString("name")
|
|
usrDomainId, _ := usr.GetString("domain_id")
|
|
usrDomainName, _ := usr.GetString("project_domain")
|
|
|
|
data.Add(jsonutils.NewString(usrDomainId), "domain", "id")
|
|
data.Add(jsonutils.NewString(usrDomainName), "domain", "name")
|
|
data.Add(jsonutils.NewStringArray(auth.AdminCredential().GetRegions()), "regions")
|
|
data.Add(jsonutils.NewBool(options.Options.EnableTotp), "system_totp_on")
|
|
|
|
var projName string
|
|
var projDomainId string
|
|
if len(pid) > 0 {
|
|
projInfo, err := modules.Projects.GetById(s, pid, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "fetchProjectById %s", pid)
|
|
}
|
|
|
|
projName, _ = projInfo.GetString("name")
|
|
projId, _ := projInfo.GetString("id")
|
|
projDomainId, _ = projInfo.GetString("domain_id")
|
|
projDomainName, _ := projInfo.GetString("project_domain")
|
|
data.Add(jsonutils.NewString(projName), "projectName")
|
|
data.Add(jsonutils.NewString(projId), "projectId")
|
|
data.Add(jsonutils.NewString(projDomainName), "projectDomain")
|
|
data.Add(jsonutils.NewString(projDomainId), "projectDomainId")
|
|
|
|
pmeta, err := projInfo.Get("metadata")
|
|
if pmeta != nil {
|
|
data.Add(pmeta, "project_meta")
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("getUserInfo modules.RoleAssignments.List")
|
|
query := jsonutils.NewDict()
|
|
query.Add(jsonutils.JSONNull, "effective")
|
|
query.Add(jsonutils.JSONNull, "include_names")
|
|
query.Add(jsonutils.JSONNull, "include_system")
|
|
query.Add(jsonutils.NewInt(0), "limit")
|
|
query.Add(jsonutils.NewString(uid), "user", "id")
|
|
roleAssigns, err := modules.RoleAssignments.List(s, query)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "get RoleAssignments list")
|
|
}
|
|
currentRoles := make([]string, 0)
|
|
projects := make(map[string]*projectRoles)
|
|
for _, roleAssign := range roleAssigns.Data {
|
|
roleId, _ := roleAssign.GetString("role", "id")
|
|
roleName, _ := roleAssign.GetString("role", "name")
|
|
projectId, _ := roleAssign.GetString("scope", "project", "id")
|
|
projectName, _ := roleAssign.GetString("scope", "project", "name")
|
|
domainId, _ := roleAssign.GetString("scope", "project", "domain", "id")
|
|
domain, _ := roleAssign.GetString("scope", "project", "domain", "name")
|
|
if projectId == pid {
|
|
currentRoles = append(currentRoles, roleName)
|
|
}
|
|
_, ok := projects[projectId]
|
|
if ok {
|
|
projects[projectId].add(roleId, roleName)
|
|
} else {
|
|
projects[projectId] = newProjectRoles(projectId, projectName, roleId, roleName, domainId, domain)
|
|
}
|
|
}
|
|
|
|
data.Add(jsonutils.NewStringArray(currentRoles), "roles")
|
|
var policies map[string][]string
|
|
projJson := jsonutils.NewArray()
|
|
for _, proj := range projects {
|
|
j, p := proj.json(
|
|
s,
|
|
usrName,
|
|
usrId,
|
|
usrDomainName,
|
|
usrDomainId,
|
|
loginIp,
|
|
)
|
|
projJson.Add(j)
|
|
if proj.id == pid {
|
|
policies = p
|
|
}
|
|
}
|
|
data.Add(projJson, "projects")
|
|
|
|
if len(pid) > 0 {
|
|
for _, scope := range []rbacscope.TRbacScope{
|
|
rbacscope.ScopeSystem,
|
|
rbacscope.ScopeDomain,
|
|
rbacscope.ScopeProject,
|
|
} {
|
|
if p, ok := policies[string(scope)]; ok {
|
|
data.Add(jsonutils.NewStringArray(p), fmt.Sprintf("%s_policies", scope))
|
|
if scope == rbacscope.ScopeSystem {
|
|
data.Add(jsonutils.NewStringArray(p), "admin_policies")
|
|
} else if scope == rbacscope.ScopeProject {
|
|
data.Add(jsonutils.NewStringArray(p), "policies")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
services := jsonutils.NewArray()
|
|
menus := jsonutils.NewArray()
|
|
k8s := jsonutils.NewArray()
|
|
|
|
curReg := s.GetRegion()
|
|
srvCat := auth.Client().GetServiceCatalog()
|
|
var allsrv []string
|
|
var alleps []mcclient.ExternalService
|
|
if srvCat != nil {
|
|
allsrv = srvCat.GetInternalServices(curReg)
|
|
alleps = srvCat.GetServicesByInterface(curReg, "console")
|
|
}
|
|
|
|
log.Infof("getUserInfo checkAgent exists")
|
|
for _, cf := range []struct {
|
|
existFunc func(*mcclient.ClientSession) (bool, error)
|
|
srvName string
|
|
}{
|
|
{
|
|
existFunc: isLBAgentExists,
|
|
srvName: "lbagent",
|
|
},
|
|
{
|
|
existFunc: isBaremetalAgentExists,
|
|
srvName: "bmagent",
|
|
},
|
|
{
|
|
existFunc: isHostAgentExists,
|
|
srvName: "hostagent",
|
|
},
|
|
{
|
|
existFunc: isEsxiAgentExists,
|
|
srvName: "esxiagent",
|
|
},
|
|
} {
|
|
exist, err := cf.existFunc(s)
|
|
if err != nil {
|
|
log.Errorf("isLBAgentExists fail %s", err)
|
|
} else if exist {
|
|
allsrv = append(allsrv, cf.srvName)
|
|
}
|
|
}
|
|
|
|
for _, srv := range allsrv {
|
|
item := jsonutils.NewDict()
|
|
item.Add(jsonutils.NewString(srv), "type")
|
|
item.Add(jsonutils.JSONTrue, "status")
|
|
services.Add(item)
|
|
}
|
|
|
|
for _, ep := range alleps {
|
|
item := jsonutils.NewDict()
|
|
item.Add(jsonutils.NewString(ep.Url), "url")
|
|
item.Add(jsonutils.NewString(ep.Name), "name")
|
|
item.Add(jsonutils.NewString(ep.Service), "service")
|
|
menus.Add(item)
|
|
}
|
|
|
|
log.Infof("getUserInfo modules.Hosts.Get")
|
|
// s2 := auth.GetSession(ctx, token, FetchRegion(req), "v2")
|
|
params := jsonutils.NewDict()
|
|
params.Add(jsonutils.NewString("host_type"), "field")
|
|
params.Add(jsonutils.NewString("system"), "scope")
|
|
params.Add(jsonutils.JSONTrue, "usable")
|
|
params.Add(jsonutils.JSONTrue, "show_emulated")
|
|
cap, err := compute_modules.Hosts.Get(s, "distinct-field", params)
|
|
if err != nil {
|
|
log.Errorf("modules.Servers.Get distinct-field fail %s", err)
|
|
} else {
|
|
hostTypes, _ := jsonutils.GetStringArray(cap, "host_type")
|
|
hypervisors := make([]string, len(hostTypes))
|
|
for i, hostType := range hostTypes {
|
|
hypervisors[i] = compute.HOSTTYPE_HYPERVISOR[hostType]
|
|
}
|
|
data.Add(jsonutils.NewStringArray(hypervisors), "hypervisors")
|
|
}
|
|
|
|
data.Add(menus, "menus")
|
|
data.Add(k8s, "k8sdashboard")
|
|
data.Add(services, "services")
|
|
|
|
if options.Options.NonDefaultDomainProjects {
|
|
data.Add(jsonutils.JSONTrue, "non_default_domain_projects")
|
|
} else {
|
|
data.Add(jsonutils.JSONFalse, "non_default_domain_projects")
|
|
}
|
|
|
|
if options.Options.EnableQuotaCheck {
|
|
data.Add(jsonutils.JSONTrue, "enable_quota_check")
|
|
} else {
|
|
data.Add(jsonutils.JSONFalse, "enable_quota_check")
|
|
}
|
|
|
|
// data.Add(jsonutils.NewString(getSsoCallbackUrl(ctx, req, idpId)), "sso_callback_url")
|
|
|
|
return data, nil
|
|
}
|
|
|
|
func (h *AuthHandlers) getPermissionDetails(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
t := AppContextToken(ctx)
|
|
|
|
_, query, body := appsrv.FetchEnv(ctx, w, req)
|
|
if body == nil {
|
|
httperrors.InvalidInputError(ctx, w, "request body is empty")
|
|
return
|
|
}
|
|
var name string
|
|
if query != nil {
|
|
name, _ = query.GetString("policy")
|
|
}
|
|
result, err := policy.ExplainRpc(ctx, t, body, name)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
appsrv.SendJSON(w, result)
|
|
}
|
|
|
|
func (h *AuthHandlers) getAdminResources(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
res := policy.GetSystemResources()
|
|
appsrv.SendJSON(w, jsonutils.Marshal(res))
|
|
}
|
|
|
|
func (h *AuthHandlers) getResources(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
res := policy.GetResources()
|
|
appsrv.SendJSON(w, jsonutils.Marshal(res))
|
|
}
|
|
|
|
func (h *AuthHandlers) doCreatePolicies(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
t := AppContextToken(ctx)
|
|
// if !utils.IsInStringArray("admin", t.GetRoles()) || t.GetProjectName() != "system" {
|
|
// httperrors.ForbiddenError(ctx, w, "not allow to create policy")
|
|
// return
|
|
// }
|
|
_, _, body := appsrv.FetchEnv(ctx, w, req)
|
|
if body == nil {
|
|
httperrors.InvalidInputError(ctx, w, "request body is empty")
|
|
return
|
|
}
|
|
s := auth.GetSession(ctx, t, FetchRegion(req))
|
|
result, err := policytool.PolicyCreate(s, body)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
appsrv.SendJSON(w, result)
|
|
}
|
|
|
|
func (h *AuthHandlers) doPatchPolicy(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
t := AppContextToken(ctx)
|
|
// if !utils.IsInStringArray("admin", t.GetRoles()) || t.GetProjectName() != "system" {
|
|
// httperrors.ForbiddenError(ctx, w, "not allow to create policy")
|
|
// return
|
|
// }
|
|
params, _, body := appsrv.FetchEnv(ctx, w, req)
|
|
if body == nil {
|
|
httperrors.InvalidInputError(ctx, w, "request body is empty")
|
|
return
|
|
}
|
|
s := auth.GetSession(ctx, t, FetchRegion(req))
|
|
result, err := policytool.PolicyPatch(s, params["<policy_id>"], body)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
appsrv.SendJSON(w, result)
|
|
}
|
|
|
|
func (h *AuthHandlers) doDeletePolicies(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
t := AppContextToken(ctx)
|
|
// if !utils.IsInStringArray("admin", t.GetRoles()) || t.GetProjectName() != "system" {
|
|
// httperrors.ForbiddenError(ctx, w, "not allow to create policy")
|
|
// return
|
|
// }
|
|
_, query, _ := appsrv.FetchEnv(ctx, w, req)
|
|
s := auth.GetSession(ctx, t, FetchRegion(req))
|
|
|
|
idlist, e := query.GetArray("id")
|
|
if e != nil || len(idlist) == 0 {
|
|
httperrors.InvalidInputError(ctx, w, "missing id")
|
|
return
|
|
}
|
|
idStrList := jsonutils.JSONArray2StringArray(idlist)
|
|
ret := make([]printutils.SubmitResult, len(idStrList))
|
|
for i := range idStrList {
|
|
err := policytool.PolicyDelete(s, idStrList[i])
|
|
if err != nil {
|
|
ret[i] = printutils.SubmitResult{
|
|
Status: 400,
|
|
Id: idStrList[i],
|
|
Data: jsonutils.NewString(err.Error()),
|
|
}
|
|
} else {
|
|
ret[i] = printutils.SubmitResult{
|
|
Status: 200,
|
|
Id: idStrList[i],
|
|
Data: jsonutils.NewDict(),
|
|
}
|
|
}
|
|
}
|
|
w.WriteHeader(207)
|
|
appsrv.SendJSON(w, modulebase.SubmitResults2JSON(ret))
|
|
}
|
|
|
|
/*
|
|
重置密码
|
|
1.验证新密码正确
|
|
2.验证原密码正确,且idp_driver为空
|
|
3.如果已开启MFA,验证 随机密码正确
|
|
4.重置密码,清除认证token
|
|
*/
|
|
func (h *AuthHandlers) resetUserPassword(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
|
t, authToken, err := fetchAuthInfo(ctx, req)
|
|
if err != nil {
|
|
httperrors.InvalidCredentialError(ctx, w, "fetchAuthInfo fail: %s", err)
|
|
return
|
|
}
|
|
|
|
_, _, body := appsrv.FetchEnv(ctx, w, req)
|
|
if body == nil {
|
|
httperrors.InvalidInputError(ctx, w, "request body is empty")
|
|
return
|
|
}
|
|
|
|
user, err := fetchUserInfoFromToken(ctx, req, t)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
input := struct {
|
|
PasswordOld string
|
|
PasswordNew string
|
|
PasswordConfirm string
|
|
Passcode string
|
|
}{}
|
|
body.Unmarshal(&input)
|
|
|
|
input.PasswordOld = decodePassword(input.PasswordOld)
|
|
input.PasswordNew = decodePassword(input.PasswordNew)
|
|
input.PasswordConfirm = decodePassword(input.PasswordConfirm)
|
|
|
|
if input.PasswordNew != input.PasswordConfirm {
|
|
httperrors.InputParameterError(ctx, w, "new password mismatch")
|
|
return
|
|
}
|
|
|
|
// 1.验证原密码正确,且idp_driver为空
|
|
if isIdpUser(user) {
|
|
httperrors.ForbiddenError(ctx, w, "not support reset user password")
|
|
return
|
|
}
|
|
|
|
cliIp := netutils2.GetHttpRequestIp(req)
|
|
_, err = auth.Client().AuthenticateWeb(t.GetUserName(), input.PasswordOld, t.GetDomainName(), "", "", cliIp)
|
|
if err != nil {
|
|
switch httperr := err.(type) {
|
|
case *httputils.JSONClientError:
|
|
if httperr.Code == 409 {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
}
|
|
httperrors.InputParameterError(ctx, w, "wrong password")
|
|
return
|
|
}
|
|
|
|
s := auth.GetAdminSession(ctx, FetchRegion(req))
|
|
// 2.如果已开启MFA,验证 随机密码正确
|
|
if isMfaEnabled(user) {
|
|
err = authToken.VerifyTotpPasscode(s, t.GetUserId(), input.Passcode)
|
|
if err != nil {
|
|
httperrors.InputParameterError(ctx, w, "invalid passcode")
|
|
return
|
|
}
|
|
}
|
|
|
|
// 3.重置密码,
|
|
params := jsonutils.NewDict()
|
|
params.Set("password", jsonutils.NewString(input.PasswordNew))
|
|
_, err = modules.UsersV3.Patch(s, t.GetUserId(), params)
|
|
if err != nil {
|
|
httperrors.GeneralServerError(ctx, w, err)
|
|
return
|
|
}
|
|
|
|
// 4. 清除认证token, logout
|
|
h.postLogoutHandler(ctx, w, req)
|
|
}
|
|
|
|
func isIdpUser(user jsonutils.JSONObject) bool {
|
|
if driver, _ := user.GetString("idp_driver"); len(driver) > 0 {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// refer: isUserEnableTotp
|
|
func isMfaEnabled(user jsonutils.JSONObject) bool {
|
|
if !options.Options.EnableTotp {
|
|
return false
|
|
}
|
|
|
|
if ok, _ := user.Bool("enable_mfa"); ok {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func decodePassword(passwd string) string {
|
|
if decPasswd, err := seclib2.AES_256.CbcDecodeBase64(passwd, []byte(agapi.DefaultEncryptKey)); err == nil && stringutils2.IsPrintableAsciiString(string(decPasswd)) {
|
|
// First try AES decryption
|
|
passwd = string(decPasswd)
|
|
} else if decPasswd, err := base64.StdEncoding.DecodeString(passwd); err == nil && stringutils2.IsPrintableAsciiString(string(decPasswd)) {
|
|
// try base64 decryption
|
|
passwd = string(decPasswd)
|
|
}
|
|
return passwd
|
|
}
|