mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
feature: verify user with sms code
This commit is contained in:
@@ -355,9 +355,14 @@ func (h *AuthHandlers) doCredentialLogin(ctx context.Context, req *http.Request,
|
||||
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")
|
||||
}
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "processSsoLoginData")
|
||||
// }
|
||||
} else if body.Contains("verify_code") { // verify by mobile
|
||||
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")
|
||||
}
|
||||
|
||||
24
pkg/apigateway/handler/verify.go
Normal file
24
pkg/apigateway/handler/verify.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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 (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
)
|
||||
|
||||
func processVerifyLoginData(uid, contactType, verifyCode string, cliIp string) (mcclient.TokenCredential, error) {
|
||||
return auth.Client().AuthenticateVerify(uid, contactType, verifyCode, "", "", "", cliIp)
|
||||
}
|
||||
@@ -32,8 +32,6 @@ type SRegionsReponse struct {
|
||||
Regions []string `json:"regions,allowempty"`
|
||||
Domains []string `json:"domains,allowempty"`
|
||||
|
||||
Captcha bool `json:"captcha,allowempty"`
|
||||
|
||||
Idps []SIdp `json:"idps,allowempty"`
|
||||
ReturnFullDomains bool `json:"return_full_domains"`
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
AUTH_METHOD_SAML = "saml"
|
||||
AUTH_METHOD_OIDC = "oidc"
|
||||
AUTH_METHOD_OAuth2 = "oauth2"
|
||||
AUTH_METHOD_VERIFY = "verify"
|
||||
|
||||
// AUTH_METHOD_ID_PASSWORD = 1
|
||||
// AUTH_METHOD_ID_TOKEN = 2
|
||||
|
||||
@@ -43,6 +43,9 @@ type ReceiverCreateInput struct {
|
||||
// description: enabled contact types for user
|
||||
// example: {"email", "mobile", "feishu", "dingtalk", "workwx"}
|
||||
EnabledContactTypes []string `json:"enabled_contact_types"`
|
||||
|
||||
// force verified if admin create the records
|
||||
ForceVerified bool `json:"force_verified"`
|
||||
}
|
||||
|
||||
type SInternationalMobile struct {
|
||||
@@ -129,6 +132,8 @@ type ReceiverUpdateInput struct {
|
||||
// description: enabled contacts for user
|
||||
// example: {"email", "mobile", "feishu", "dingtalk", "workwx"}
|
||||
EnabledContactTypes []string `json:"enabled_contact_types"`
|
||||
|
||||
ForceVerified bool `json:"force_verified"`
|
||||
}
|
||||
|
||||
type ReceiverTriggerVerifyInput struct {
|
||||
|
||||
@@ -26,12 +26,14 @@ import (
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
notify_api "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/keystone/driver"
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/keystone/options"
|
||||
"yunion.io/x/onecloud/pkg/keystone/saml"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
notify_modules "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
"yunion.io/x/onecloud/pkg/util/s3auth"
|
||||
)
|
||||
@@ -391,6 +393,26 @@ func authUserByAccessKeyV3(ctx context.Context, input mcclient.SAuthenticationIn
|
||||
return usrExt, credential.ProjectId, aksk, nil
|
||||
}
|
||||
|
||||
func authUserByVerify(ctx context.Context, input mcclient.SAuthenticationInputV3) (*api.SUserExtended, error) {
|
||||
extUser, err := models.UserManager.FetchUserExtended(input.Auth.Identity.Verify.Uid, "", "", "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "FetchUserExtended")
|
||||
}
|
||||
s, err := GetDefaulAdminSession(ctx, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetDefaultAdminSession")
|
||||
}
|
||||
verifyInput := notify_api.ReceiverVerifyInput{
|
||||
ContactType: input.Auth.Identity.Verify.ContactType,
|
||||
Token: input.Auth.Identity.Verify.VerifyCode,
|
||||
}
|
||||
_, err = notify_modules.NotifyReceiver.PerformAction(s, extUser.Id, "verify", jsonutils.Marshal(verifyInput))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Verify")
|
||||
}
|
||||
return extUser, nil
|
||||
}
|
||||
|
||||
// +onecloud:swagger-gen-route-method=POST
|
||||
// +onecloud:swagger-gen-route-path=/v3/auth/tokens
|
||||
// +onecloud:swagger-gen-route-tag=authentication
|
||||
@@ -445,6 +467,11 @@ func AuthenticateV3(ctx context.Context, input mcclient.SAuthenticationInputV3)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "authUserByOAuth2")
|
||||
}
|
||||
case api.AUTH_METHOD_VERIFY:
|
||||
user, err = authUserByVerify(ctx, input)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "authUserByVerify")
|
||||
}
|
||||
default:
|
||||
// auth by other methods, e.g. password , etc...
|
||||
user, err = authUserByIdentityV3(ctx, input)
|
||||
|
||||
26
pkg/keystone/tokens/util.go
Normal file
26
pkg/keystone/tokens/util.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
func GetDefaulAdminSession(ctx context.Context, region string) (*mcclient.ClientSession, error) {
|
||||
return models.GetDefaultClientSession(ctx, GetDefaultAdminCredToken(), region), nil
|
||||
}
|
||||
@@ -17,11 +17,10 @@ package util
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/keystone/models"
|
||||
"yunion.io/x/onecloud/pkg/keystone/tokens"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
func GetDefaulAdminSession(ctx context.Context, region string) (*mcclient.ClientSession, error) {
|
||||
return models.GetDefaultClientSession(ctx, tokens.GetDefaultAdminCredToken(), region), nil
|
||||
return tokens.GetDefaulAdminSession(ctx, region)
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ type SAuthenticationIdentity struct {
|
||||
// | saml | 作为SAML 2.0 SP通过IDP认证 |
|
||||
// | oidc | 作为OpenID Connect/OAuth2 Client认证 |
|
||||
// | oauth2 | OAuth2认证 |
|
||||
// | verify | 手机短信或邮箱认证 |
|
||||
//
|
||||
Methods []string `json:"methods,omitempty"`
|
||||
// 当认证方式为password时,通过该字段提供密码认证信息
|
||||
@@ -127,7 +128,12 @@ type SAuthenticationIdentity struct {
|
||||
} `json:"oidc_auth,omitempty"`
|
||||
OAuth2 struct {
|
||||
Code string `json:"code,omitempty"`
|
||||
}
|
||||
} `json:"oauth2,omitempty"`
|
||||
Verify struct {
|
||||
Uid string `json:"uid,omitempty"`
|
||||
VerifyCode string `json:"verify_code,omitempty"`
|
||||
ContactType string `json:"contact_type,omitempty"`
|
||||
} `json:"mobile,omitempty"`
|
||||
}
|
||||
|
||||
type SAuthenticationInputV3 struct {
|
||||
|
||||
51
pkg/mcclient/verify.go
Normal file
51
pkg/mcclient/verify.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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 mcclient
|
||||
|
||||
import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
func (this *Client) AuthenticateVerify(uid, contactType, verifyCode string, projectId, projectName, projectDomain string, cliIp string) (TokenCredential, error) {
|
||||
aCtx := SAuthContext{
|
||||
// OpenID Connect auth must comes from Web
|
||||
Source: AuthSourceWeb,
|
||||
Ip: cliIp,
|
||||
}
|
||||
return this.authenticateVerifyWithContext(uid, contactType, verifyCode, projectId, projectName, projectDomain, aCtx)
|
||||
}
|
||||
|
||||
func (this *Client) authenticateVerifyWithContext(uid, contactType, verifyCode string, projectId, projectName, projectDomain string, aCtx SAuthContext) (TokenCredential, error) {
|
||||
if this.AuthVersion() != "v3" {
|
||||
return nil, httperrors.ErrNotSupported
|
||||
}
|
||||
input := SAuthenticationInputV3{}
|
||||
input.Auth.Identity.Methods = []string{api.AUTH_METHOD_VERIFY}
|
||||
input.Auth.Identity.Verify.Uid = uid
|
||||
input.Auth.Identity.Verify.ContactType = contactType
|
||||
input.Auth.Identity.Verify.VerifyCode = verifyCode
|
||||
if len(projectId) > 0 {
|
||||
input.Auth.Scope.Project.Id = projectId
|
||||
}
|
||||
if len(projectName) > 0 {
|
||||
input.Auth.Scope.Project.Name = projectName
|
||||
if len(projectDomain) > 0 {
|
||||
input.Auth.Scope.Project.Domain.Name = projectDomain
|
||||
}
|
||||
}
|
||||
input.Auth.Context = aCtx
|
||||
return this._authV3Input(input)
|
||||
}
|
||||
@@ -845,13 +845,15 @@ func (r *SReceiver) CustomizeCreate(ctx context.Context, userCred mcclient.Token
|
||||
}
|
||||
// 需求:管理后台新建的联系人,手机号和邮箱无需进行校验
|
||||
// 方案:检查请求者对于创建联系人 是否具有system scope
|
||||
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
|
||||
if allowScope == rbacutils.ScopeSystem {
|
||||
if r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
if input.ForceVerified {
|
||||
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
|
||||
if allowScope == rbacutils.ScopeSystem {
|
||||
if r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -914,21 +916,23 @@ func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCreden
|
||||
log.Errorf("PushCache: %v", err)
|
||||
}
|
||||
// 管理后台修改联系人,如果修改或者启用手机号和邮箱,无需进行校验
|
||||
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
|
||||
if allowScope == rbacutils.ScopeSystem {
|
||||
// 修改并启用
|
||||
if len(input.Email) != 0 && input.Email != r.Email && r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if len(mobile) != 0 && mobile != r.Mobile && r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
}
|
||||
// 从禁用变启用
|
||||
if !originEmailEnable.Bool() && r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if !originMobileEnable.Bool() && r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
if input.ForceVerified {
|
||||
allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate)
|
||||
if allowScope == rbacutils.ScopeSystem {
|
||||
// 修改并启用
|
||||
if len(input.Email) != 0 && input.Email != r.Email && r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if len(mobile) != 0 && mobile != r.Mobile && r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
}
|
||||
// 从禁用变启用
|
||||
if !originEmailEnable.Bool() && r.EnabledEmail.Bool() {
|
||||
r.VerifiedEmail = tristate.True
|
||||
}
|
||||
if !originMobileEnable.Bool() && r.EnabledMobile.Bool() {
|
||||
r.VerifiedMobile = tristate.True
|
||||
}
|
||||
}
|
||||
}
|
||||
r.Mobile = mobile
|
||||
@@ -947,7 +951,6 @@ func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCrede
|
||||
if err != nil {
|
||||
log.Errorf("unable to StartSubcontactPullTask: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *SReceiver) StartSubcontactPullTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error {
|
||||
@@ -1056,11 +1059,11 @@ func (r *SReceiver) PerformTriggerVerify(ctx context.Context, userCred mcclient.
|
||||
return nil, r.StartSubcontactPullTask(ctx, userCred, params, "")
|
||||
}
|
||||
_, err := VerificationManager.Create(ctx, r.Id, input.ContactType)
|
||||
if err == ErrVerifyFrequently {
|
||||
/*if err == ErrVerifyFrequently {
|
||||
return nil, httperrors.NewForbiddenError("Send verify message too frequently, please try again later")
|
||||
}
|
||||
}*/
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "VerifyManager.Create")
|
||||
}
|
||||
|
||||
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/notify/options"
|
||||
)
|
||||
|
||||
@@ -54,7 +55,7 @@ type SVerification struct {
|
||||
Token string `width:"200" nullable:"false"`
|
||||
}
|
||||
|
||||
var ErrVerifyFrequently = errors.Error("Send validation messages too frequently")
|
||||
var ErrVerifyFrequently = errors.Wrap(httperrors.ErrTooManyRequests, "Send validation messages too frequently")
|
||||
|
||||
func (vm *SVerificationManager) generateVerifyToken() string {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
@@ -27,6 +27,8 @@ import (
|
||||
apis "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
||||
"yunion.io/x/onecloud/pkg/notify"
|
||||
"yunion.io/x/onecloud/pkg/notify/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
@@ -103,6 +105,20 @@ func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneMo
|
||||
self.taskFailed(ctx, receiver, reason)
|
||||
return
|
||||
}
|
||||
// sync email and mobile to keystone
|
||||
s := auth.GetSession(ctx, self.UserCred, "")
|
||||
mobile := receiver.Mobile
|
||||
if strings.HasPrefix(mobile, "+86 ") {
|
||||
mobile = strings.TrimSpace(mobile[4:])
|
||||
}
|
||||
params := map[string]string{
|
||||
"email": receiver.Email,
|
||||
"mobile": receiver.Mobile,
|
||||
}
|
||||
_, err = identity.UsersV3.Update(s, receiver.Id, jsonutils.Marshal(params))
|
||||
if err != nil {
|
||||
log.Errorf("update user email and mobile fail %s", err)
|
||||
}
|
||||
// success
|
||||
receiver.SetStatus(self.UserCred, apis.RECEIVER_STATUS_READY, "")
|
||||
logclient.AddActionLogWithContext(ctx, receiver, logclient.ACT_PULL_SUBCONTACT, "", self.UserCred, true)
|
||||
|
||||
Reference in New Issue
Block a user