package federation import ( "context" "strings" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "xorm.io/builder" ) // HookTask represents a hook task. // exact copy of models/webhook/hooktask.go when this migration was created // - xorm:"-" fields deleted type FederatedHost struct { ID int64 `xorm:"pk autoincr"` isBlocked bool HostFqdn string `xorm:"UNIQUE(s) INDEX"` } func GetFederatdHost(ctx context.Context, hostFqdn string) (*FederatedHost, error) { rec := new(FederatedHost) _, err := db.GetEngine(ctx). Table("federated_host").Where("host_fqdn = ?", hostFqdn).Get(rec) if err != nil { return nil, err } return rec, nil } func FederatedHostExists(ctx context.Context, hostFqdn string) (bool, error) { rec := new(FederatedHost) exists, err := db.GetEngine(ctx). Table("federated_host").Where("host_fqdn = ?", hostFqdn).Get(rec) if err != nil { return false, err } return exists, nil } func (host *FederatedHost) Save(ctx context.Context) error { _, err := db.GetEngine(ctx). Insert(host) return err } type FederatedUser struct { ID int64 `xorm:"pk autoincr"` UserID int64 `xorm:"INDEX"` ExternalID string `xorm:"UNIQUE(s) INDEX"` FederationHostID int64 `xorm:"INDEX"` } func CreateFederatedUser(ctx context.Context, alias string, website string, hostname string) (*user.User, error) { engine := db.GetEngine(ctx) // create FederatedHost exists, err := FederatedHostExists(ctx, hostname) if err != nil { return nil, err } var federatedHost FederatedHost if exists { x, err := GetFederatdHost(ctx, hostname) federatedHost = *x if err != nil { return nil, err } } else { federatedHost := new(FederatedHost) federatedHost.HostFqdn = hostname if err = federatedHost.Save(ctx); err != nil { return nil, err } } // create user.User u := new(user.User) u.Name = "@" + alias + "@" + hostname //panic(u.Name) u.Email = alias + "@" + hostname u.Website = website u.KeepEmailPrivate = true exist, err := user.GetUser(ctx, u) if err != nil { return nil, err } if exist { return u, nil // TODO: must also check for federatedUser existence } if err = createUser(ctx, u); err != nil { return nil, err } federatedUser := new(FederatedUser) federatedUser.ExternalID = u.Name federatedUser.UserID = u.ID federatedUser.FederationHostID = federatedHost.ID exist, err = engine.Get(federatedUser) if err != nil { return nil, err } if !exist { _, err = engine.Insert(federatedUser) if err != nil { return nil, err } } return u, nil } func createUser(ctx context.Context, u *user.User) error { // set system defaults u.Visibility = setting.Service.DefaultUserVisibilityMode u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification u.MaxRepoCreation = -1 u.Theme = setting.UI.DefaultTheme u.IsRestricted = setting.Service.DefaultUserIsRestricted u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm) // Ensure consistency of the dates. if u.UpdatedUnix < u.CreatedUnix { u.UpdatedUnix = u.CreatedUnix } // validate data if err := user.ValidateUser(u); err != nil { return err } if err := user.ValidateEmail(u.Email); err != nil { return err } ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() isExist, err := user.IsUserExist(ctx, 0, u.Name) if err != nil { return err } else if isExist { return user.ErrUserAlreadyExist{u.Name} } isExist, err = user.IsEmailUsed(ctx, u.Email) if err != nil { return err } else if isExist { return user.ErrEmailAlreadyUsed{ Email: u.Email, } } // prepare for database u.LowerName = strings.ToLower(u.Name) u.AvatarEmail = u.Email if u.Rands, err = user.GetUserSalt(); err != nil { return err } if u.Passwd != "" { if err = u.SetPassword(u.Passwd); err != nil { return err } } else { u.Salt = "" u.PasswdHashAlgo = "" } // save changes to database if err = user.DeleteUserRedirect(ctx, u.Name); err != nil { return err } if u.CreatedUnix == 0 { // Caller expects auto-time for creation & update timestamps. err = db.Insert(ctx, u) } else { // Caller sets the timestamps themselves. They are responsible for ensuring // both `CreatedUnix` and `UpdatedUnix` are set appropriately. _, err = db.GetEngine(ctx).NoAutoTime().Insert(u) } if err != nil { return err } // insert email address if err := db.Insert(ctx, &user.EmailAddress{ UID: u.ID, Email: u.Email, LowerEmail: strings.ToLower(u.Email), IsActivated: u.IsActive, IsPrimary: true, }); err != nil { return err } return committer.Commit() } func GetRemoteUsersWithNoLocalFollowers(ctx context.Context, olderThan time.Duration, page int) ([]user.User, error) { limit := 40 offset := page * limit var users []user.User err := db.GetEngine(ctx). Table("user"). Where("num_followers = 0"). And(builder.Lt{"user.created_unix": time.Now().Add(-olderThan).Unix()}). Join("inner", "federated_user", "federated_user.user_id = user.id"). Limit(limit, offset). Find(&users) if err != nil { log.Trace("Error: GetRemoteUserWithNoLocalFollowers: %w", err) return nil, err } return users, nil } func GetRemotePersons(ctx context.Context, page int) ([]FederatedUser, error) { limit := 1 offset := page * limit var federatedUsers []FederatedUser err := db.GetEngine(ctx). Table("federated_user"). Limit(limit, offset). Find(&federatedUsers) // TODO: this doesn't work, so fetching federated_user and then getting user. How to make this work? // var users []user.User // err := db.GetEngine(ctx). // Table("user"). // Join("inner", "federated_user", "federated_user.user_id = user.id"). // Limit(limit, offset). // Find(&users) if err != nil { log.Trace("Error: GetRemotePersons: %w", err) return nil, err } return federatedUsers, nil }