Merge pull request #9948 from rainzm/keystone/user_lock

feat(keystone,apigateway): return a readable message when the user is locked or disabled
This commit is contained in:
Zexi Li
2021-01-20 20:05:44 +08:00
committed by GitHub
6 changed files with 57 additions and 53 deletions
+1 -1
View File
@@ -309,7 +309,7 @@ func (h *AuthHandlers) doCredentialLogin(ctx context.Context, req *http.Request,
if err != nil {
switch httperr := err.(type) {
case *httputils.JSONClientError:
if httperr.Code == 409 || httperr.Code == 429 {
if httperr.Code == 409 || httperr.Code == 429 || httperr.Code == 423 {
return nil, err
}
}
+6 -5
View File
@@ -31,11 +31,12 @@ type SUserExtended struct {
Email string
Mobile string
LocalId int
LocalName string
DomainName string
DomainEnabled bool
IsLocal bool
LocalId int
LocalName string
LocalFailedAuthCount int
DomainName string
DomainEnabled bool
IsLocal bool
// IdpId string
// IdpName string
}
+6
View File
@@ -84,6 +84,9 @@ const (
ErrTooManyAttempts = errors.Error("TooManyFailedAttempts")
ErrTooManyRequests = errors.Error("TooManyRequests")
ErrUserLocked = errors.Error("User Locked")
ErrUserDisabled = errors.Error("User Disabled")
ErrUnsupportedProtocol = errors.Error("UnsupportedProtocol")
ErrPolicyDefinition = errors.Error("PolicyDefinitionError")
@@ -160,6 +163,9 @@ var (
ErrTooManyAttempts: 429,
ErrTooManyRequests: 429,
ErrUserLocked: 423,
ErrUserDisabled: 423,
ErrPolicyDefinition: 409,
}
)
+1
View File
@@ -254,6 +254,7 @@ func (manager *SUserManager) FetchUserExtended(userId, userName, domainId, domai
users.Field("is_system_account"),
localUsers.Field("id", "local_id"),
localUsers.Field("name", "local_name"),
localUsers.Field("failed_auth_count", "local_failed_auth_count"),
domains.Field("name", "domain_name"),
domains.Field("enabled", "domain_enabled"),
// idmappings.Field("domain_id", "idp_id"),
+42 -46
View File
@@ -19,7 +19,6 @@ import (
"database/sql"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
@@ -30,6 +29,7 @@ import (
"yunion.io/x/onecloud/pkg/keystone/driver"
"yunion.io/x/onecloud/pkg/keystone/models"
"yunion.io/x/onecloud/pkg/keystone/options"
o "yunion.io/x/onecloud/pkg/keystone/options"
"yunion.io/x/onecloud/pkg/keystone/saml"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/s3auth"
@@ -73,7 +73,7 @@ func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdent
}
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 user domain specified, try to find user domain
q := models.UserManager.Query().Equals("name", ident.Password.User.Name).IsTrue("enabled")
q := models.UserManager.Query().Equals("name", ident.Password.User.Name)
usrCnt, err := q.CountWithError()
if err != nil {
return nil, errors.Wrap(err, "Query user by name")
@@ -93,51 +93,48 @@ func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdent
return nil, errors.Wrap(err, "Query user")
}
ident.Password.User.Domain.Id = usr.DomainId
idps, err := models.IdentityProviderManager.FetchIdentityProvidersByUserId(usr.Id, api.PASSWORD_PROTECTED_IDPS)
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
}
}
} else {
usrExt, err := models.UserManager.FetchUserExtended(ident.Password.User.Id, ident.Password.User.Name,
ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "UserManager.FetchUserExtended")
ident.Password.User.Id = usr.Id
}
}
if err == sql.ErrNoRows {
// no such user locally, query domain idp
domain, err := models.DomainManager.FetchDomain(ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
if err != nil {
return nil, errors.Wrap(err, "DomainManager.FetchDomain")
}
mapping, err := models.IdmappingManager.FetchFirstEntity(domain.Id, api.IdMappingEntityDomain)
if err != nil {
return nil, errors.Wrap(err, "IdmappingManager.FetchEntity")
}
idpId = mapping.IdpId
usrExt, err := models.UserManager.FetchUserExtended(ident.Password.User.Id, ident.Password.User.Name,
ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
if err != nil && err != sql.ErrNoRows {
return nil, errors.Wrap(err, "UserManager.FetchUserExtended")
}
// checn enable
if !usrExt.Enabled {
if usrExt.IsLocal && usrExt.LocalFailedAuthCount > o.Options.PasswordErrorLockCount {
// user locked
return nil, errors.Wrap(httperrors.ErrUserLocked, "please contact the administrator")
}
// user disabled
return nil, errors.Wrap(httperrors.ErrUserLocked, "please contact the administrator")
}
if err == sql.ErrNoRows {
// no such user locally, query domain idp
domain, err := models.DomainManager.FetchDomain(ident.Password.User.Domain.Id, ident.Password.User.Domain.Name)
if err != nil {
return nil, errors.Wrap(err, "DomainManager.FetchDomain")
}
mapping, err := models.IdmappingManager.FetchFirstEntity(domain.Id, api.IdMappingEntityDomain)
if err != nil {
return nil, errors.Wrap(err, "IdmappingManager.FetchEntity")
}
idpId = mapping.IdpId
} else {
// user exists, query user's idp
idps, err := models.IdentityProviderManager.FetchIdentityProvidersByUserId(usrExt.Id, api.PASSWORD_PROTECTED_IDPS)
if err != nil {
return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProvidersByUserId")
}
if len(idps) == 0 {
idpId = api.DEFAULT_IDP_ID
} else if len(idps) == 1 {
idpId = idps[0].Id
} else {
// user exists, query user's idp
idps, err := models.IdentityProviderManager.FetchIdentityProvidersByUserId(usrExt.Id, api.PASSWORD_PROTECTED_IDPS)
if err != nil {
return nil, errors.Wrap(err, "IdentityProviderManager.FetchIdentityProvidersByUserId")
}
if len(idps) == 0 {
idpId = api.DEFAULT_IDP_ID
} else if len(idps) == 1 {
idpId = idps[0].Id
} else {
return nil, sqlchemy.ErrDuplicateEntry
}
return nil, sqlchemy.ErrDuplicateEntry
}
}
@@ -177,7 +174,6 @@ func authUserByIdentity(ctx context.Context, ident mcclient.SAuthenticationIdent
if idp.Status == api.IdentityDriverStatusDisconnected {
idp.MarkConnected(ctx, models.GetDefaultAdminCred())
}
return usr, nil
}
@@ -429,7 +425,7 @@ func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3)
// auth by other methods, e.g. password , etc...
user, err = authUserByIdentityV3(ctx, input)
if err != nil {
return nil, errors.Wrap(err, "authUserByIdentityV3")
return nil, err
}
}
+1 -1
View File
@@ -89,7 +89,7 @@ func authenticateTokensV3(ctx context.Context, w http.ResponseWriter, r *http.Re
switch errors.Cause(err) {
case sqlchemy.ErrDuplicateEntry:
httperrors.ConflictError(ctx, w, "duplicate username")
case httperrors.ErrTooManyAttempts, httperrors.ErrUserNotFound:
case httperrors.ErrTooManyAttempts, httperrors.ErrUserNotFound, httperrors.ErrUserDisabled, httperrors.ErrUserLocked:
httperrors.GeneralServerError(ctx, w, err)
default:
httperrors.UnauthorizedError(ctx, w, "unauthorized %s", err)