fix: user with sso idp fail to login with password (#7552)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2020-08-13 11:24:46 +08:00
committed by GitHub
co-authored by Qiu Jian
parent a02611db12
commit 320d7cabcd
6 changed files with 42 additions and 59 deletions
+26 -22
View File
@@ -60,6 +60,13 @@ func getSsoLinkCallbackUrl() string {
return options.Options.SsoLinkCallbackUrl
}
func getSsoUserNotFoundCallbackUrl() string {
if options.Options.SsoUserNotFoundCallbackUrl == "" {
return getSsoAuthCallbackUrl()
}
return options.Options.SsoUserNotFoundCallbackUrl
}
func (h *AuthHandlers) getIdpSsoRedirectUri(ctx context.Context, w http.ResponseWriter, req *http.Request) {
params := appctx.AppContextParams(ctx)
idpId := params["<idp_id>"]
@@ -164,41 +171,38 @@ func (h *AuthHandlers) handleSsoLogin(ctx context.Context, w http.ResponseWriter
appsrv.DisableClientCache(w)
var referer string
var idpUserId string
if len(idpLinkUser) > 0 {
// link with existing user
err := linkWithExistingUser(ctx, req, idpId, idpLinkUser, body)
referer := getSsoLinkCallbackUrl()
refererUrl, _ := url.Parse(referer)
if refererUrl == nil {
refererUrl, _ = url.Parse(idpReferer)
}
if err != nil {
log.Debugf("error: %s", err)
}
redirUrl := generateRedirectUrl(refererUrl, idpStateQs, err, "", "")
// success, do redirect
appsrv.SendRedirect(w, redirUrl)
err = linkWithExistingUser(ctx, req, idpId, idpLinkUser, body)
referer = getSsoLinkCallbackUrl()
} else {
// ordinary login
referer := getSsoAuthCallbackUrl()
refererUrl, _ := url.Parse(referer)
if refererUrl == nil {
refererUrl, _ = url.Parse(idpReferer)
}
var idpUserId string
err = h.doLogin(ctx, w, req, body)
if err != nil {
if errors.Cause(err) == httperrors.ErrUserNotFound {
idpUserId = findExtUserId(err.Error())
if len(idpUserId) == 0 {
err = httputils.NewJsonClientError(400, string(httperrors.ErrInputParameter), "empty external user id")
} else {
referer = getSsoUserNotFoundCallbackUrl()
}
}
log.Debugf("error: %s", err)
}
redirUrl := generateRedirectUrl(refererUrl, idpStateQs, err, idpId, idpUserId)
appsrv.SendRedirect(w, redirUrl)
if referer == "" {
referer = getSsoAuthCallbackUrl()
}
}
refererUrl, _ := url.Parse(referer)
if refererUrl == nil {
refererUrl, _ = url.Parse(idpReferer)
}
if err != nil {
log.Debugf("error: %s refererUrl: %s", err, refererUrl)
}
redirUrl := generateRedirectUrl(refererUrl, idpStateQs, err, idpId, idpUserId)
appsrv.SendRedirect(w, redirUrl)
}
func generateRedirectUrl(originUrl *url.URL, stateQs jsonutils.JSONObject, err error, idpId, idpUserId string) string {
@@ -224,7 +228,7 @@ func generateRedirectUrl(originUrl *url.URL, stateQs jsonutils.JSONObject, err e
qs.(*jsonutils.JSONDict).Add(jsonutils.NewString("error"), "result")
if len(idpUserId) > 0 {
qs.(*jsonutils.JSONDict).Add(jsonutils.NewString(idpId), "idp_id")
qs.(*jsonutils.JSONDict).Add(jsonutils.NewString(idpUserId), "idp_user_id")
qs.(*jsonutils.JSONDict).Add(jsonutils.NewString(idpUserId), "idp_entity_id")
}
} else {
qs.(*jsonutils.JSONDict).Add(jsonutils.NewString("success"), "result")
+2
View File
@@ -33,6 +33,8 @@ type GatewayOptions struct {
SsoLinkCallbackUrl string `help:"SSO idp link user callback URL"`
LoginCallbackParam string `help:"Redirect callback parameter name after successful login"`
SsoUserNotFoundCallbackUrl string `help:"failure callback URL when SSO idp link user not found"`
common_options.CommonOptions `"request_worker_count->default":"32"`
}
+1 -1
View File
@@ -35,7 +35,7 @@ type SUserExtended struct {
LocalName string
DomainName string
DomainEnabled bool
IsLocal bool
// IsLocal bool
// IdpId string
// IdpName string
}
+2 -12
View File
@@ -276,24 +276,14 @@ func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domai
extUser := api.SUserExtended{}
err := q.First(&extUser)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "query")
}
idMaps, err := IdmappingManager.FetchEntities(extUser.Id, api.IdMappingEntityUser)
if len(idMaps) > 0 {
extUser.IsLocal = false
} else {
extUser.IsLocal = true
}
return &extUser, nil
}
func VerifyPassword(user *api.SUserExtended, passwd string) error {
if user.IsLocal {
return localUserVerifyPassword(user, passwd)
} else {
return fmt.Errorf("not implemented")
}
return localUserVerifyPassword(user, passwd)
}
func localUserVerifyPassword(user *api.SUserExtended, passwd string) error {
+8 -16
View File
@@ -20,6 +20,8 @@ import (
"fmt"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
@@ -71,29 +73,17 @@ func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdent
return nil, ErrEmptyAuth
}
if len(ident.Password.User.Name) > 0 && len(ident.Password.User.Id) == 0 && len(ident.Password.User.Domain.Id) == 0 && len(ident.Password.User.Domain.Name) == 0 {
// no use domain specified, try to find use domain
users := models.UserManager.Query().SubQuery()
idMappings := models.IdmappingManager.Query().SubQuery()
q := users.Query()
q = q.LeftJoin(idMappings, sqlchemy.Equals(idMappings.Field("public_id"), users.Field("id")))
q = q.Filter(sqlchemy.Equals(users.Field("name"), ident.Password.User.Name))
q = q.Filter(sqlchemy.OR(
sqlchemy.IsNull(idMappings.Field("domain_id")),
sqlchemy.In(idMappings.Field("domain_id"), models.IdentityProviderManager.FetchPasswordProtectedIdpIdsQuery()),
))
// no user domain specified, try to find user domain
q := models.UserManager.Query().Equals("name", ident.Password.User.Name).IsTrue("enabled")
usrCnt, err := q.CountWithError()
if err != nil {
return nil, errors.Wrap(err, "Query user by name")
}
if usrCnt > 1 {
log.Errorf("find %d user with name %s", usrCnt, ident.Password.User.Name)
return nil, sqlchemy.ErrDuplicateEntry
} else if usrCnt == 0 {
/*idp, err := models.IdentityProviderManager.GetAutoCreateUserProvider()
if err != nil {
return nil, errors.Wrap(err, "IdentityProviderManager.GetAutoCreateUserProvider")
}
idpId = idp.Id
*/
log.Errorf("find no user with name %s", ident.Password.User.Name)
return nil, sqlchemy.ErrEmptyQuery
} else {
// userCnt == 1
@@ -108,11 +98,13 @@ func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdent
if err != nil {
return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProvidersByUserId")
}
log.Debugf("user %s idps: %s", ident.Password.User.Name, jsonutils.Marshal(idps))
if len(idps) == 0 {
idpId = api.DEFAULT_IDP_ID
} else if len(idps) == 1 {
idpId = idps[0].Id
} else {
log.Errorf("find %d password idps for user %s", len(idps), ident.Password.User.Name)
return nil, sqlchemy.ErrDuplicateEntry
}
}
+3 -8
View File
@@ -256,14 +256,9 @@ func (t *SAuthToken) getTokenV3(
token.Token.User.Name = user.Name
token.Token.User.Domain.Id = user.DomainId
token.Token.User.Domain.Name = user.DomainName
if user.IsLocal {
lastPass, err := models.PasswordManager.FetchLastPassword(user.LocalId)
if err != nil {
return nil, errors.Wrap(err, "FetchLastPassword")
}
if lastPass != nil && !lastPass.ExpiresAt.IsZero() {
token.Token.User.PasswordExpiresAt = lastPass.ExpiresAt
}
lastPass, _ := models.PasswordManager.FetchLastPassword(user.LocalId)
if lastPass != nil && !lastPass.ExpiresAt.IsZero() {
token.Token.User.PasswordExpiresAt = lastPass.ExpiresAt
}
token.Token.User.Displayname = user.Displayname
token.Token.User.Email = user.Email