diff --git a/pkg/keystone/driver/sql/sql.go b/pkg/keystone/driver/sql/sql.go index 2aac5063bc..598620e062 100644 --- a/pkg/keystone/driver/sql/sql.go +++ b/pkg/keystone/driver/sql/sql.go @@ -16,15 +16,23 @@ package sql import ( "context" + "time" "github.com/pkg/errors" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/sets" + api "yunion.io/x/onecloud/pkg/apis/identity" + noapi "yunion.io/x/onecloud/pkg/apis/notify" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/keystone/driver" "yunion.io/x/onecloud/pkg/keystone/models" o "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules/notify" ) type SSQLDriver struct { @@ -64,6 +72,7 @@ func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthent localUser.SaveFailedAuth() if o.Options.PasswordErrorLockCount > 0 && localUser.FailedAuthCount > o.Options.PasswordErrorLockCount { models.UserManager.LockUser(usrExt.Id, "too many failed auth attempts") + sql.alertNotify(ctx, usrExt, time.Now()) return nil, errors.Wrap(httperrors.ErrTooManyAttempts, "user locked") } return nil, errors.Wrap(err, "usrExt.VerifyPassword") @@ -72,6 +81,76 @@ func (sql *SSQLDriver) Authenticate(ctx context.Context, ident mcclient.SAuthent return usrExt, nil } +func (sql *SSQLDriver) alertNotify(ctx context.Context, uext *api.SUserExtended, triggerTime time.Time) { + // get all users + daUserIds, err := getDomainAdminUserIds(uext.DomainName) + if err != nil { + log.Errorf("unable to get user with role domainadmin in domain %s: %v", uext.DomainName, err) + } + aUserIds, err := getAdminUserIds() + if err != nil { + log.Errorf("unable to get user with role admin: %v", err) + } + userSet := sets.NewString(daUserIds...) + userSet.Insert(aUserIds...) + userSet.Insert(uext.Id) + + data := jsonutils.NewDict() + data.Set("user", jsonutils.NewString(uext.Name)) + data.Set("domain", jsonutils.NewString(uext.DomainName)) + metadata := map[string]interface{}{ + "trigger_time": triggerTime, + } + // user + p := notifyclient.SNotifyParams{ + RecipientId: userSet.UnsortedList(), + Priority: notify.NotifyPriorityCritical, + Event: notifyclient.USER_LOGIN_EXCEPTION, + Data: data, + Tag: noapi.NOTIFICATION_TAG_ALERT, + Metadata: metadata, + IgnoreNonexistentReceiver: true, + } + notifyclient.NotifyWithTag(ctx, p) +} + +func fetchRoleId(name string) (string, error) { + id := struct { + Id string + }{} + q := models.RoleManager.Query().Equals("name", name) + err := q.First(&id) + if err != nil { + return "", err + } + return id.Id, nil +} + +func getAdminUserIds() ([]string, error) { + return getUserIdsWithRole("admin", "") +} + +func getUserIdsWithRole(roleName string, domainId string) ([]string, error) { + roleId, err := fetchRoleId(roleName) + if err != nil { + return nil, errors.Wrapf(err, "unable to fetch roleid of %s", roleName) + } + ras, _, err := models.AssignmentManager.FetchAll("", "", roleId, "", "", domainId, []string{}, []string{}, []string{}, []string{}, []string{}, []string{}, false, true, false, false, false, 0, 0) + if err != nil { + return nil, err + } + userIds := make([]string, 0, len(ras)) + for i := range ras { + userIds = append(userIds, ras[i].User.Id) + } + log.Infof("%s User: %v", roleName, userIds) + return userIds, nil +} + +func getDomainAdminUserIds(domainId string) ([]string, error) { + return getUserIdsWithRole("domainadmin", domainId) +} + func (sql *SSQLDriver) Sync(ctx context.Context) error { return nil } diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index 5ef9615843..8a7ab324b7 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -1167,3 +1167,27 @@ func (user *SUser) PerformUnlinkIdp( } return nil, nil } + +func GetUserLangForKeyStone(uids []string) (map[string]string, error) { + simpleUsers := make([]struct { + Id string + Lang string + }, 0, len(uids)) + q := UserManager.Query() + if len(uids) == 0 { + return nil, nil + } else if len(uids) == 1 { + q = q.Equals("id", uids[0]) + } else { + q = q.In("id", uids) + } + err := q.All(&simpleUsers) + if err != nil { + return nil, err + } + ret := make(map[string]string, len(simpleUsers)) + for i := range simpleUsers { + ret[simpleUsers[i].Id] = simpleUsers[i].Lang + } + return ret, nil +} diff --git a/pkg/keystone/service/service.go b/pkg/keystone/service/service.go index 59c3c77b33..1a4a68a2ac 100644 --- a/pkg/keystone/service/service.go +++ b/pkg/keystone/service/service.go @@ -25,6 +25,7 @@ import ( app_common "yunion.io/x/onecloud/pkg/cloudcommon/app" "yunion.io/x/onecloud/pkg/cloudcommon/cronman" "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/keystone/cronjobs" @@ -34,6 +35,7 @@ import ( "yunion.io/x/onecloud/pkg/keystone/saml" _ "yunion.io/x/onecloud/pkg/keystone/tasks" "yunion.io/x/onecloud/pkg/keystone/tokens" + "yunion.io/x/onecloud/pkg/keystone/util" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -50,6 +52,8 @@ func StartService() { policy.DefaultPolicyFetcher = localPolicyFetcher logclient.DefaultSessionGenerator = models.GetDefaultClientSession cronman.DefaultAdminSessionGenerator = models.GetDefaultAdminCred + notifyclient.AdminSessionGenerator = util.GetDefaulAdminSession + notifyclient.UserLangFetcher = models.GetUserLangForKeyStone models.InitSyncWorkers()