diff --git a/cmd/climc/shell/notify/receiver.go b/cmd/climc/shell/notify/receiver.go index a58f9eae9a..859cb1e308 100644 --- a/cmd/climc/shell/notify/receiver.go +++ b/cmd/climc/shell/notify/receiver.go @@ -16,6 +16,9 @@ package notify import ( "yunion.io/x/onecloud/cmd/climc/shell" + identity_api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/mcclient" + identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity" modules "yunion.io/x/onecloud/pkg/mcclient/modules/notify" options "yunion.io/x/onecloud/pkg/mcclient/options/notify" ) @@ -36,4 +39,32 @@ func init() { cmd.PerformClass("get-types", new(options.ReceiverGetTypeOptions)) cmd.Perform("get-subscription", new(options.ReceiverGetSubscriptionOptions)) cmd.GetProperty(new(options.SReceiverRoleContactType)) + + type SyncUserContactOptions struct { + USERID string `json:"user_id"` + Mobile string `json:"mobile"` + Email string `json:"email"` + } + R(&SyncUserContactOptions{}, "sync-user-contacts", "Sync user contact", func(s *mcclient.ClientSession, args *SyncUserContactOptions) error { + userObj, err := identity_modules.UsersV3.Get(s, args.USERID, nil) + if err != nil { + return err + } + user := identity_api.UserDetails{} + err = userObj.Unmarshal(&user) + if err != nil { + return err + } + if len(args.Mobile) == 0 { + args.Mobile = user.Mobile + } + if len(args.Email) == 0 { + args.Email = user.Email + } + err = modules.NotifyReceiver.SyncUserContact(s, user.Id, args.Mobile, args.Email) + if err != nil { + return err + } + return nil + }) } diff --git a/pkg/apis/notify/receiver.go b/pkg/apis/notify/receiver.go index 1ef6d1b2ee..710062852e 100644 --- a/pkg/apis/notify/receiver.go +++ b/pkg/apis/notify/receiver.go @@ -19,6 +19,8 @@ import ( "regexp" "strings" + "yunion.io/x/pkg/tristate" + "yunion.io/x/onecloud/pkg/apis" ) @@ -65,6 +67,9 @@ var ( func ParseInternationalMobile(mobile string) SInternationalMobile { matchs := pareser.FindStringSubmatch(mobile) if len(matchs) == 0 { + if len(mobile) == 0 { + return SInternationalMobile{} + } return SInternationalMobile{ AreaCode: defaultAreaCode, Mobile: mobile, @@ -85,7 +90,7 @@ func (im *SInternationalMobile) AcceptExtMobile() { // 对传入的手机号去除地区编号 func moveAreaCode(mobile string) string { // 所有地区编号 - allArea := `283|282|281|280|269|268|267|266|265|264|263|262|261|260|259|258|257|256|255|254|253|252|251|250|249|248|247|246|245|244|243|242|241|240|239|238|237|236|235|234|233|232|231|230|229|228|227|226|225|224|223|222|221|220|219|218|217|216|215|214|213|212|211|210|98|95|94|93|92|91|90|86|84|82|81|66|65|64|63|62|61|60|58|57|56|55|54|53|52|51|49|48|47|46|45|44|43|41|40|39|36|34|33|32|31|30|27|20|7|1` + allArea := `852|283|282|281|280|269|268|267|266|265|264|263|262|261|260|259|258|257|256|255|254|253|252|251|250|249|248|247|246|245|244|243|242|241|240|239|238|237|236|235|234|233|232|231|230|229|228|227|226|225|224|223|222|221|220|219|218|217|216|215|214|213|212|211|210|98|95|94|93|92|91|90|86|84|82|81|66|65|64|63|62|61|60|58|57|56|55|54|53|52|51|49|48|47|46|45|44|43|41|40|39|36|34|33|32|31|30|27|20|7|1` temp := strings.Split(allArea, "|") for _, area := range temp { if strings.HasPrefix(mobile, "+"+area) { @@ -111,6 +116,9 @@ func moveExtStr(mobile string) string { } func (im SInternationalMobile) String() string { + if len(im.Mobile) == 0 { + return "" + } if im.AreaCode == "" { return im.Mobile } @@ -159,6 +167,17 @@ type ReceiverUpdateInput struct { // example: example@gmail.com Email string `json:"email"` + // swagger:ignore + EnabledEmail tristate.TriState `json:"enabled_email"` + // swagger:ignore + VerifiedEmail tristate.TriState `json:"verified_email"` + // swagger:ignore + EnabledMobile tristate.TriState `json:"enabled_mobile"` + // swagger:ignore + VerifiedMobile tristate.TriState `json:"verified_mobile"` + // swagger:ignore + Mobile string `json:"mobile"` + InternationalMobile SInternationalMobile `json:"international_mobile"` // description: enabled contacts for user diff --git a/pkg/apis/notify/receiver_test.go b/pkg/apis/notify/receiver_test.go index 52e419cd67..8ce6987646 100644 --- a/pkg/apis/notify/receiver_test.go +++ b/pkg/apis/notify/receiver_test.go @@ -23,15 +23,15 @@ func TestMobileExt(t *testing.T) { }{ { SInternationalMobile{ - "+8612345678901", - "+86", + Mobile: "+8612345678901", + AreaCode: "+86", }, "12345678901", }, { SInternationalMobile{ - "+8612345678901;ext=2", - "+86", + Mobile: "+8612345678901;ext=2", + AreaCode: "+86", }, "12345678901", }, @@ -56,6 +56,20 @@ func TestMobileExt(t *testing.T) { }, "12345678901", }, + { + SInternationalMobile{ + "13811111111", + "", + }, + "13811111111", + }, + { + SInternationalMobile{ + "+85213811111111", + "", + }, + "13811111111", + }, } for _, c := range cases { diff --git a/pkg/mcclient/modules/notify/mod_notify.go b/pkg/mcclient/modules/notify/mod_notify.go index 9e395ed80d..787e421b4a 100644 --- a/pkg/mcclient/modules/notify/mod_notify.go +++ b/pkg/mcclient/modules/notify/mod_notify.go @@ -15,16 +15,28 @@ package notify import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/httputils" + + notify_apis "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" "yunion.io/x/onecloud/pkg/mcclient/modules" ) +type ReceiverManager struct { + modulebase.ResourceManager +} + type ConfigsManager struct { modulebase.ResourceManager } var ( - NotifyReceiver modulebase.ResourceManager + NotifyReceiver ReceiverManager NotifyConfig modulebase.ResourceManager NotifyRobot modulebase.ResourceManager Notification modulebase.ResourceManager @@ -34,13 +46,72 @@ var ( Configs ConfigsManager ) +func (rm *ReceiverManager) SyncUserContact(s *mcclient.ClientSession, userId string, mobile string, email string) error { + recv, err := rm.GetById(s, userId, nil) + if err != nil { + if httputils.ErrorCode(err) == 404 { + // not created yet, do create + newRecv := notify_apis.ReceiverCreateInput{ + UID: userId, + } + if len(mobile) > 0 { + newRecv.InternationalMobile.Mobile = mobile + newRecv.EnabledContactTypes = append(newRecv.EnabledContactTypes, "mobile") + } + if len(email) > 0 { + newRecv.Email = email + newRecv.EnabledContactTypes = append(newRecv.EnabledContactTypes, "email") + } + _, err := rm.Create(s, jsonutils.Marshal(newRecv)) + if err != nil { + // create failed + return errors.Wrap(err, "create receiver") + } + // success + } else { + return errors.Wrap(err, "GetById") + } + } else { + // receiver exists + fmt.Println("receiver exists", recv.String()) + recvObj := notify_apis.ReceiverDetails{} + err := recv.Unmarshal(&recvObj) + if err != nil { + return errors.Wrap(err, "Unmarshal ReceiverDetails") + } + updateRecv := notify_apis.ReceiverUpdateInput{} + changeMobile := false + changeEmail := false + if mobile != "" && recvObj.InternationalMobile.Mobile != mobile { + updateRecv.InternationalMobile.Mobile = mobile + updateRecv.EnabledContactTypes = append(updateRecv.EnabledContactTypes, "mobile") + changeMobile = true + } + if email != "" && recvObj.Email != email { + updateRecv.Email = email + updateRecv.EnabledContactTypes = append(updateRecv.EnabledContactTypes, "email") + changeEmail = true + } + if changeMobile || changeEmail { + _, err = rm.Update(s, userId, jsonutils.Marshal(updateRecv)) + if err != nil { + return errors.Wrap(err, "update receiver") + } + } + } + + return nil +} + func init() { - NotifyReceiver = modules.NewNotifyv2Manager( - "receiver", - "receivers", - []string{"ID", "Name", "Domain_Id", "Project_Domain", "Email", "International_Mobile", "Enabled_Contact_Types", "Verified_Infos"}, - []string{}, - ) + NotifyReceiver = ReceiverManager{ + ResourceManager: modules.NewNotifyv2Manager( + "receiver", + "receivers", + []string{"ID", "Name", "Domain_Id", "Project_Domain", "Email", "International_Mobile", "Enabled_Contact_Types", "Verified_Infos"}, + []string{}, + ), + } modules.Register(&NotifyReceiver) NotifyConfig = modules.NewNotifyv2Manager( diff --git a/pkg/notify/models/receiver.go b/pkg/notify/models/receiver.go index c0de812672..d75f829ea4 100644 --- a/pkg/notify/models/receiver.go +++ b/pkg/notify/models/receiver.go @@ -94,7 +94,7 @@ type SReceiver struct { Email string `width:"128" nullable:"false" create:"optional" update:"user" get:"user" list:"user"` // swagger:ignore - Mobile string `width:"32" nullable:"false" create:"optional"` + Mobile string `width:"32" nullable:"false" create:"optional" update:"user"` Lang string `width:"8" charset:"ascii" nullable:"false" list:"user" update:"user"` // swagger:ignore @@ -618,9 +618,34 @@ func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.To return input, httperrors.NewInputParameterError("invalid email") } // validate mobile - input.InternationalMobile.AcceptExtMobile() - if ok := len(input.InternationalMobile.Mobile) == 0 || LaxMobileRegexp.MatchString(input.InternationalMobile.Mobile); !ok { - return input, httperrors.NewInputParameterError("invalid mobile") + if len(input.InternationalMobile.Mobile) > 0 { + input.InternationalMobile.AcceptExtMobile() + if ok := len(input.InternationalMobile.Mobile) == 0 || LaxMobileRegexp.MatchString(input.InternationalMobile.Mobile); !ok { + return input, httperrors.NewInputParameterError("invalid mobile") + } + input.Mobile = input.InternationalMobile.String() + } else { + input.InternationalMobile.AreaCode = "" + } + if input.ForceVerified { + allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate) + if allowScope != rbacscope.ScopeSystem { + return input, httperrors.ErrNotSufficientPrivilege + } + } + if len(input.Email) > 0 && input.Email != r.Email { + if input.ForceVerified { + r.VerifiedEmail = tristate.True + } else { + r.VerifiedEmail = tristate.False + } + } + if len(input.Mobile) > 0 && input.Mobile != r.Mobile { + if input.ForceVerified { + r.VerifiedMobile = tristate.True + } else { + r.VerifiedMobile = tristate.False + } } for _, cType := range input.EnabledContactTypes { @@ -633,78 +658,29 @@ func (r *SReceiver) ValidateUpdateData(ctx context.Context, userCred mcclient.To return input, nil } -func (r *SReceiver) PreUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { - r.SVirtualResourceBase.PreUpdate(ctx, userCred, query, data) - originEmailEnable, originMobileEnable := r.EnabledEmail, r.EnabledMobile +func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { + r.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data) + var input api.ReceiverUpdateInput data.Unmarshal(&input) - if len(input.Email) != 0 && input.Email != r.Email { - db.Update(r, func() error { - r.VerifiedEmail = tristate.False - return nil - }) - r.VerifiedEmail = tristate.False - subs, _ := r.GetSubContacts() - for i := range subs { - if subs[i].ParentContactType == api.EMAIL { - db.Update(&subs[i], func() error { - subs[i].Verified = tristate.False - subs[i].VerifiedNote = "email changed, re-verify" - return nil - }) - } - } - } - mobile := input.InternationalMobile.String() - if len(mobile) != 0 && mobile != r.Mobile { - db.Update(r, func() error { - r.VerifiedMobile = tristate.False - return nil - }) - subs, _ := r.GetSubContacts() - for i := range subs { - if subs[i].ParentContactType == api.MOBILE { - db.Update(&subs[i], func() error { - subs[i].Verified = tristate.False - subs[i].VerifiedNote = "mobile changed, re-verify" - return nil - }) - } - } - } - // 管理后台修改联系人,如果修改或者启用手机号和邮箱,无需进行校验 - if input.ForceVerified { - allowScope, _ := policy.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, ReceiverManager.KeywordPlural(), policy.PolicyActionCreate) - if allowScope == rbacscope.ScopeSystem { - db.Update(r, func() error { - // 修改并启用 - 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 - } + subs, _ := r.GetSubContacts() + for i := range subs { + if subs[i].ParentContactType == api.EMAIL && subs[i].Verified.IsTrue() && r.VerifiedEmail.IsFalse() { + db.Update(&subs[i], func() error { + subs[i].Verified = tristate.False + subs[i].VerifiedNote = "email changed, re-verify" + return nil + }) + } else if subs[i].ParentContactType == api.MOBILE && subs[i].Verified.IsTrue() && r.VerifiedMobile.IsFalse() { + db.Update(&subs[i], func() error { + subs[i].Verified = tristate.False + subs[i].VerifiedNote = "mobile changed, re-verify" return nil }) } } - r.Mobile = mobile - err := ReceiverManager.TableSpec().InsertOrUpdate(ctx, r) - if err != nil { - log.Errorf("InsertOrUpdate: %v", err) - } -} -func (r *SReceiver) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { - r.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data) cTypes := jsonutils.GetQueryStringArray(data, "enabled_contact_types") err := r.StartSubcontactPullTask(ctx, userCred, cTypes, "") if err != nil { @@ -1080,7 +1056,6 @@ func (r *SReceiver) SetContact(cType string, contact string) error { r.Mobile = contact return nil }) - r.Mobile = contact default: subs, _ := r.GetSubContacts() for i := range subs { diff --git a/pkg/notify/tasks/subcontact_pull_task.go b/pkg/notify/tasks/subcontact_pull_task.go index e3f7ea6e62..ac96505fef 100644 --- a/pkg/notify/tasks/subcontact_pull_task.go +++ b/pkg/notify/tasks/subcontact_pull_task.go @@ -80,8 +80,12 @@ func (self *SubcontactPullTask) OnInit(ctx context.Context, obj db.IStandaloneMo // 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:]) + if strings.HasPrefix(mobile, "+") { + spaceIdx := strings.Index(mobile, " ") + if spaceIdx > 0 { + mobile = mobile[spaceIdx+1:] + } + mobile = strings.TrimSpace(mobile) } params := map[string]string{ "email": receiver.Email,