Files
cloudpods/pkg/apigateway/handler/auth.go
2019-11-01 20:07:52 +08:00

1049 lines
30 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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"
"fmt"
"net/http"
"strings"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"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"
"yunion.io/x/onecloud/pkg/appctx"
"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"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/onecloud/pkg/util/rbacutils"
)
func AppContextToken(ctx context.Context) mcclient.TokenCredential {
val := ctx.Value(appctx.AppContextKey(constants.AUTH_TOKEN))
if val == nil {
return nil
}
return val.(mcclient.TokenCredential)
}
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.listTotpRecoveryQuestions, "recovery"),
)
h.AddByMethod(POST, nil,
NewHP(h.resetTotpSecrets, "credential"),
NewHP(h.validatePasscode, "passcode"),
NewHP(h.resetTotpRecoveryQuestions, "recovery"),
NewHP(h.postLoginHandler, "login"),
NewHP(h.postLogoutHandler, "logout"),
)
// auth middleware handler
h.AddByMethod(GET, FetchAuthToken,
NewHP(h.getUser, "user"),
NewHP(h.getPermissionDetails, "permissions"),
NewHP(h.getAdminResources, "admin_resources"),
NewHP(h.getResources, "scoped_resources"),
)
h.AddByMethod(POST, FetchAuthToken,
NewHP(h.resetUserPassword, "password"),
NewHP(h.getPermissionDetails, "permissions"),
NewHP(h.doCreatePolicies, "policies"),
)
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) (*jsonutils.JSONDict, error) {
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")
}
regionsJson := jsonutils.NewStringArray(regions)
s := auth.GetAdminSession(ctx, regions[0], "")
filters := jsonutils.NewDict()
filters.Add(jsonutils.NewInt(1000), "limit")
result, e := modules.Domains.List(s, filters)
if e != nil {
return nil, errors.Wrap(e, "list domain")
}
domains := jsonutils.NewArray()
for _, d := range result.Data {
dn, e := d.Get("name")
if e == nil {
if status, err := d.Bool("enabled"); err == nil && status {
domains.Add(dn)
}
}
}
resp := jsonutils.NewDict()
resp.Add(domains, "domains")
resp.Add(regionsJson, "regions")
filters = jsonutils.NewDict()
filters.Add(jsonutils.NewStringArray([]string{"cas"}), "driver")
filters.Add(jsonutils.JSONTrue, "enabled")
filters.Add(jsonutils.NewInt(1000), "limit")
idps, err := modules.IdentityProviders.List(s, filters)
if err != nil {
return nil, errors.Wrap(err, "list idp")
}
retIdps := make([]jsonutils.JSONObject, 0)
for i := range idps.Data {
retIdp := jsonutils.NewDict()
id, _ := idps.Data[i].GetString("id")
name, _ := idps.Data[i].GetString("name")
driver, _ := idps.Data[i].GetString("driver")
retIdp.Add(jsonutils.NewString(id), "id")
retIdp.Add(jsonutils.NewString(name), "name")
retIdp.Add(jsonutils.NewString(driver), "driver")
conf, err := modules.IdentityProviders.GetSpecific(s, id, "config", nil)
if err != nil {
return nil, errors.Wrap(err, "idp get config spec")
}
retIdp.Update(conf)
retIdps = append(retIdps, retIdp)
}
resp.Add(jsonutils.NewArray(retIdps...), "idps")
return resp, 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(w, err)
return
}
appsrv.SendJSON(w, resp)
}
func (h *AuthHandlers) getUser(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t := AppContextToken(ctx)
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
data, err := getUserInfo(ctx, s, t, req)
if err != nil {
httperrors.NotFoundError(w, err.Error())
return
}
body := jsonutils.NewDict()
body.Add(data, "data")
appsrv.SendJSON(w, body)
}
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, w http.ResponseWriter, req *http.Request, body jsonutils.JSONObject) (mcclient.TokenCredential, bool) {
otpVerified := false
tenantId, e := body.GetString("tenantId")
if e != nil {
httperrors.InvalidInputError(w, "not found tenantId in body")
return nil, otpVerified
}
authTokenStr := getAuthToken(req)
if len(authTokenStr) == 0 {
httperrors.InvalidCredentialError(w, "not found auth token")
return nil, otpVerified
}
token := clientman.TokenMan.Get(authTokenStr)
if token == nil || !token.IsValid() {
httperrors.InvalidCredentialError(w, "auth token %q is invalid", authTokenStr)
return nil, otpVerified
}
if isUserEnableTotp(ctx, req, token) {
totp := clientman.TokenMan.GetTotp(authTokenStr)
if !totp.IsVerified() {
httperrors.UnauthorizedError(w, "invalid totp token %q", authTokenStr)
return nil, otpVerified
} else {
otpVerified = true
}
}
token, e = auth.Client().SetProject(tenantId, "", "", token)
if e != nil {
httperrors.InvalidCredentialError(w, "failed to change project")
return nil, otpVerified
}
return token, otpVerified
}
func isUserEnableTotp(ctx context.Context, req *http.Request, token mcclient.TokenCredential) bool {
if !options.Options.EnableTotp {
return false
}
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
usr, err := modules.UsersV3.Get(s, token.GetUserId(), nil)
if err != nil {
return false
}
return jsonutils.QueryBoolean(usr, "enable_mfa", true)
}
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
cliIp := netutils2.GetHttpRequestIp(req)
if body.Contains("username") {
uname, _ := body.GetString("username")
if h.preLoginHook != nil {
if err = h.preLoginHook(ctx, req, uname, body); err != nil {
return nil, err
}
}
var passwd string
passwd, err = body.GetString("password")
if err != nil {
return nil, httperrors.NewInputParameterError("get password in body")
}
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("cas_ticket") {
ticket, _ := body.GetString("cas_ticket")
if len(ticket) == 0 {
return nil, httperrors.NewInputParameterError("cas_ticket is empty")
}
token, err = auth.Client().AuthenticateCAS(ticket, "", "", "", cliIp)
} else {
return nil, httperrors.NewInputParameterError("missing credential")
}
if err != nil {
switch httperr := err.(type) {
case *httputils.JSONClientError:
if httperr.Code == 409 {
return nil, err
}
}
return nil, httperrors.NewInvalidCredentialError("username/password incorrect")
}
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 {
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(ctx context.Context, w http.ResponseWriter, req *http.Request, token mcclient.TokenCredential) bool {
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
usr, err := modules.UsersV3.Get(s, token.GetUserId(), nil)
if err != nil {
httperrors.GeneralServerError(w, err)
return false
}
if !jsonutils.QueryBoolean(usr, "allow_web_console", true) {
httperrors.ForbiddenError(w, "forbidden user %q login from web", usr.String())
return false
}
return true
}
func saveCookie(w http.ResponseWriter, name, val string, expire time.Time, base64 bool) {
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}
http.SetCookie(w, cookie)
}
func getCookie(r *http.Request, name string) 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 {
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) {
cookie := &http.Cookie{Name: name, Expires: time.Now(), Path: "/", MaxAge: -1, HttpOnly: false}
http.SetCookie(w, cookie)
}
type PreLoginFunc func(ctx context.Context, req *http.Request, uname string, body jsonutils.JSONObject) error
func (h *AuthHandlers) postLoginHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
body, e := appsrv.FetchJSON(req)
if e != nil {
httperrors.InvalidInputError(w, "fetch json for request: %v", e)
return
}
var token mcclient.TokenCredential
otpVerified := false
if body.Contains("tenantId") { // switch project
token, otpVerified = doTenantLogin(ctx, w, req, body)
} else if body.Contains("username") || body.Contains("cas_ticket") {
// user/password authenticate
// cas authentication
token, e = h.doCredentialLogin(ctx, req, body)
if e != nil {
httperrors.GeneralServerError(w, e)
return
}
} else {
httperrors.InvalidInputError(w, "no login credential")
return
}
if token == nil {
return
}
if !isUserAllowWebconsole(ctx, w, req, token) {
return
}
//if len(token.GetProjectId()) == 0 {
// no vaid project, return 403
// httperrors.NoProjectError(w, "no valid project")
// return
//}
tid := clientman.TokenMan.Save(token)
// 切换项目时如果之前totp已经验证通过自动放行
if otpVerified {
totp := clientman.TokenMan.GetTotp(tid)
totp.MarkVerified()
clientman.TokenMan.SaveTotp(tid)
}
// log.Debugf("auth %s token %s", tid, token)
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
authCookie, err := getUserAuthCookie(ctx, s, token, tid, req)
if err != nil {
httperrors.GeneralServerError(w, err)
return
}
saveCookie(w, constants.YUNION_AUTH_COOKIE, authCookie, token.GetExpires(), true)
if len(token.GetProjectId()) > 0 {
if body.Contains("isadmin") {
adminVal := "false"
if policy.PolicyManager.IsScopeCapable(token, rbacutils.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, rbacutils.TRbacScope(scopeStr)) {
scopeStr = string(rbacutils.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)
}
setAuthHeader(w, tid)
// 开启Totp的状态下如果用户未设置
qrcode := ""
if isUserEnableTotp(ctx, req, token) {
qrcode, err = initializeUserTotpCred(s, token)
if err != nil {
httperrors.GeneralServerError(w, err)
return
}
}
appsrv.Send(w, qrcode)
}
func (h *AuthHandlers) postLogoutHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
tid := getAuthToken(req)
if len(tid) > 0 {
clientman.TokenMan.Remove(tid)
}
clearCookie(w, constants.YUNION_AUTH_COOKIE)
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 ""
}
func fetchDomain(req *http.Request) string {
r, e := req.Cookie("domain")
if e != nil {
return ""
}
return r.Value
}
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(scope rbacutils.TRbacScope, user, userId, domain, domainId string, ip string) mcclient.TokenCredential {
return &mcclient.SSimpleToken{
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(), ","),
Context: mcclient.SAuthContext{
Ip: ip,
},
}
// return policy.PolicyManager.IsScopeCapable(&t, scope)
}
func (this *projectRoles) getRoles() []string {
roles := make([]string, 0)
for _, r := range this.roles {
roles = append(roles, r.name)
}
return roles
}
func (this *projectRoles) json(user, userId, domain, domainId string, ip string) jsonutils.JSONObject {
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")
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)
}
obj.Add(roles, "roles")
for _, scope := range []rbacutils.TRbacScope{
rbacutils.ScopeSystem,
rbacutils.ScopeDomain,
rbacutils.ScopeProject,
} {
token := this.getToken(scope, user, userId, domain, domainId, ip)
matches := policy.PolicyManager.MatchedPolicies(scope, token)
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 == rbacutils.ScopeSystem {
if len(matches) > 0 {
obj.Add(jsonutils.JSONTrue, "admin_capable")
} else {
obj.Add(jsonutils.JSONFalse, "admin_capable")
}
}
}
return obj
}
func getUserAuthCookie(ctx context.Context, s *mcclient.ClientSession, token mcclient.TokenCredential, sid string, req *http.Request) (string, error) {
// info, err := getUserInfo(s, token, req)
// if err != nil {
// return "", err
// }
info := jsonutils.NewDict()
info.Add(jsonutils.NewTimeString(token.GetExpires()), "exp")
info.Add(jsonutils.NewString(sid), "session")
info.Add(jsonutils.NewBool(isUserEnableTotp(ctx, req, token)), "totp_on") // 用户totp 开启状态。 True已开启|False(未开启)
info.Add(jsonutils.NewBool(options.Options.EnableTotp), "system_totp_on") // 全局totp 开启状态。 True已开启|False(未开启)
return info.String(), nil
}
func getLBAgentInfo(s *mcclient.ClientSession, token mcclient.TokenCredential) (*jsonutils.JSONDict, error) {
lbagents, err := modules.LoadbalancerAgents.List(s, nil)
if err != nil {
return nil, errors.Wrapf(err, "user %s get lbagent", token.GetUserName())
}
item := jsonutils.NewDict()
item.Add(jsonutils.NewString(""), "name")
item.Add(jsonutils.NewString("lbagent"), "type")
item.Add(jsonutils.JSONTrue, "as_menu")
if len(lbagents.Data) > 0 {
item.Add(jsonutils.JSONTrue, "status")
} else {
item.Add(jsonutils.JSONFalse, "status")
}
return item, nil
}
func getUserInfo(ctx context.Context, s *mcclient.ClientSession, token mcclient.TokenCredential, req *http.Request) (*jsonutils.JSONDict, error) {
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())
}
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", "idp_driver",
"password_expires_at", "failed_auth_count", "failed_auth_at",
} {
v, e := usr.Get(k)
if e == nil {
data.Add(v, k)
}
}
data.Add(jsonutils.NewString(token.GetDomainId()), "domain", "id")
data.Add(jsonutils.NewString(token.GetDomainName()), "domain", "name")
data.Add(jsonutils.NewStringArray(auth.AdminCredential().GetRegions()), "regions")
data.Add(jsonutils.NewStringArray(token.GetRoles()), "roles")
data.Add(jsonutils.NewString(token.GetProjectName()), "projectName")
data.Add(jsonutils.NewString(token.GetProjectId()), "projectId")
data.Add(jsonutils.NewString(token.GetProjectDomain()), "projectDomain")
data.Add(jsonutils.NewString(token.GetProjectDomainId()), "projectDomainId")
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(token.GetUserId()), "user", "id")
roleAssigns, err := modules.RoleAssignments.List(s, query)
if err != nil {
return nil, errors.Wrapf(err, "get RoleAssignments list")
}
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")
_, ok := projects[projectId]
if ok {
projects[projectId].add(roleId, roleName)
} else {
projects[projectId] = newProjectRoles(projectId, projectName, roleId, roleName, domainId, domain)
}
}
projJson := jsonutils.NewArray()
for _, proj := range projects {
projJson.Add(proj.json(
token.GetUserName(),
token.GetUserId(),
token.GetDomainName(),
token.GetDomainId(),
token.GetLoginIp(),
))
}
data.Add(projJson, "projects")
for _, scope := range []rbacutils.TRbacScope{
rbacutils.ScopeSystem,
rbacutils.ScopeDomain,
rbacutils.ScopeProject,
} {
p := policy.PolicyManager.MatchedPolicies(scope, token)
data.Add(jsonutils.NewStringArray(p), fmt.Sprintf("%s_policies", scope))
if scope == rbacutils.ScopeSystem {
data.Add(jsonutils.NewStringArray(p), "admin_policies")
} else if scope == rbacutils.ScopeProject {
data.Add(jsonutils.NewStringArray(p), "policies")
}
}
allPolicies := policy.PolicyManager.AllPolicies()
data.Add(jsonutils.Marshal(allPolicies), "all_policies")
services := jsonutils.NewArray()
menus := jsonutils.NewArray()
k8s := jsonutils.NewArray()
adminToken := auth.AdminCredential()
curReg := FetchRegion(req)
allsrv := adminToken.GetInternalServices(curReg)
alleps := auth.Client().GetServiceCatalog().GetServicesByInterface(curReg, "console")
if allsrv != nil && len(allsrv) > 0 {
for _, srv := range allsrv {
item := jsonutils.NewDict()
item.Add(jsonutils.NewString(""), "name")
item.Add(jsonutils.NewString(srv), "type")
item.Add(jsonutils.JSONTrue, "status")
if srv == "notify" {
item.Add(jsonutils.JSONFalse, "as_menu")
} else {
item.Add(jsonutils.JSONTrue, "as_menu")
}
services.Add(item)
}
} else {
log.Errorf("fail to find services????: %#v %s", adminToken, curReg)
}
lb, err := getLBAgentInfo(s, adminToken)
if err != nil {
log.Errorf("getLBAgentInfo fail %s", err)
} else {
services.Add(lb)
}
if alleps != nil {
for _, ep := range alleps {
item := jsonutils.NewDict()
item.Add(jsonutils.NewString(ep.Url), "url")
item.Add(jsonutils.NewString(ep.Name), "name")
menus.Add(item)
}
}
s2 := auth.GetSession(ctx, token, FetchRegion(req), "v2")
cap, err := modules.Capabilities.List(s2, nil)
if err != nil {
log.Errorf("modules.Capabilities.List fail %s", err)
} else {
hypervisors, _ := cap.Data[0].Get("hypervisors")
data.Add(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")
}
return data, nil
}
func getUserHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
t := AppContextToken(ctx)
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
data, err := getUserInfo(ctx, s, t, req)
if err != nil {
httperrors.NotFoundError(w, err.Error())
return
}
body := jsonutils.NewDict()
body.Add(data, "data")
appsrv.SendJSON(w, body)
}
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(w, "body is empty")
return
}
var name string
if query != nil {
name, _ = query.GetString("policy")
}
result, err := policy.PolicyManager.ExplainRpc(t, body, name)
if err != nil {
httperrors.GeneralServerError(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(w, "not allow to create policy")
// return
// }
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(w, "body is empty")
return
}
s := auth.GetSession(ctx, t, FetchRegion(req), "")
result, err := policytool.PolicyCreate(s, body)
if err != nil {
httperrors.GeneralServerError(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(w, "not allow to create policy")
// return
// }
params, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(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(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(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(w, "missing id")
return
}
idStrList := jsonutils.JSONArray2StringArray(idlist)
ret := make([]modulebase.SubmitResult, len(idStrList))
for i := range idStrList {
err := policytool.PolicyDelete(s, idStrList[i])
if err != nil {
ret[i] = modulebase.SubmitResult{
Status: 400,
Id: idStrList[i],
Data: jsonutils.NewString(err.Error()),
}
} else {
ret[i] = modulebase.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) {
ctx, err := SetAuthToken(ctx, w, req)
if err != nil {
httperrors.InvalidCredentialError(w, "set auth token %v", err)
return
}
t := AppContextToken(ctx)
s := auth.GetAdminSession(ctx, FetchRegion(req), "")
_, _, body := appsrv.FetchEnv(ctx, w, req)
if body == nil {
httperrors.InvalidInputError(w, "body is empty")
return
}
uid := t.GetUserId()
if len(uid) == 0 {
httperrors.ConflictError(w, "uid is empty")
return
}
user, err := modules.UsersV3.Get(s, uid, nil)
if err != nil {
httperrors.GeneralServerError(w, err)
return
}
oldPwd, _ := body.GetString("password_old")
newPwd, _ := body.GetString("password_new")
confirmPwd, _ := body.GetString("password_confirm")
passcode, _ := body.GetString("passcode")
// 1.验证新密码正确
if len(newPwd) < 6 {
httperrors.InputParameterError(w, "new password must have at least 6 characters")
return
}
if newPwd != confirmPwd {
httperrors.InputParameterError(w, "new password mismatch")
return
}
// 2.验证原密码正确且idp_driver为空
if isLdapUser(user) {
httperrors.ForbiddenError(w, "not support reset ldap user password")
return
}
cliIp := netutils2.GetHttpRequestIp(req)
_, err = auth.Client().AuthenticateWeb(t.GetUserName(), oldPwd, t.GetDomainName(), "", "", cliIp)
if err != nil {
switch httperr := err.(type) {
case *httputils.JSONClientError:
if httperr.Code == 409 {
httperrors.GeneralServerError(w, err)
return
}
}
httperrors.InputParameterError(w, "密码错误")
return
}
// 3.如果已开启MFA验证 随机密码正确
tid := getAuthToken(req)
if isMfaEnabled(user) {
totp := clientman.TokenMan.GetTotp(tid)
err = totp.VerifyTotpPasscode(s, uid, passcode)
if err != nil {
httperrors.InputParameterError(w, "invalid passcode")
return
}
}
// 4.重置密码清除认证token
params := jsonutils.NewDict()
params.Set("password", jsonutils.NewString(newPwd))
ret, err := modules.UsersV3.Patch(s, uid, params)
if err != nil {
httperrors.GeneralServerError(w, err)
return
} else {
clientman.TokenMan.Remove(tid)
}
appsrv.SendJSON(w, ret)
}
func isLdapUser(user jsonutils.JSONObject) bool {
if driver, _ := user.GetString("idp_driver"); driver == "ldap" {
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
}