mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-22 04:33:42 +09:00
feat: manage bot accounts from the admin UI, API and CLI (#38966)
Adds first-class bot accounts (`UserTypeBot`): local, password-less
users for automation that authenticate only with access tokens.
1. Admin UI: create bots, filter users by type, manage a bot's access
tokens, convert between user and bot
2. API: `POST /admin/users/{username}/convert-type`, and user objects
gain a GitHub-compatible `type` (`User`, `Organization`, `Bot`)
3. CLI: `gitea admin user change-type`, `--user-type` accepts `User` or
`Bot` case-insensitively
4. Converting keeps the password, 2FA, OAuth2 grants and access tokens,
and since sign-in rejects bots, converting back restores the account.
Only local, non-admin accounts can be converted, and conversions are
audited
5. Session, reverse proxy, SSPI, external source and password reset
sign-in reject non-individual users, so a bot never gets an interactive
session
6. Bots receive no notifications or emails
Co-authored-by: Nicolas <bircni@icloud.com>
Co-authored-by: joestump <joe@joestump.net>
Co-authored-by: Joe Stump <joe@stu.mp>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
co-authored by
Nicolas
joestump
Joe Stump
silverwind
Lunny Xiao
parent
db7dbd5a6b
commit
3bec08f998
@@ -120,8 +120,7 @@ func mailIssueCommentBatch(ctx context.Context, comment *mailComment, users []*u
|
||||
|
||||
langMap := make(map[string][]*user_model.User)
|
||||
for _, user := range users {
|
||||
if !user.IsActive {
|
||||
// Exclude deactivated users
|
||||
if !user.IsMailable() {
|
||||
continue
|
||||
}
|
||||
// At this point we exclude:
|
||||
@@ -205,8 +204,7 @@ func SendIssueAssignedMail(ctx context.Context, issue *issues_model.Issue, doer
|
||||
|
||||
langMap := make(map[string][]*user_model.User)
|
||||
for _, user := range recipients {
|
||||
if !user.IsActive {
|
||||
// don't send emails to inactive users
|
||||
if !user.IsMailable() {
|
||||
continue
|
||||
}
|
||||
langMap[user.Language] = append(langMap[user.Language], user)
|
||||
|
||||
@@ -38,8 +38,7 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.
|
||||
|
||||
langMap := make(map[string][]*user_model.User)
|
||||
for _, user := range users {
|
||||
if !user.IsActive {
|
||||
// don't send emails to inactive users
|
||||
if !user.IsMailable() {
|
||||
continue
|
||||
}
|
||||
langMap[user.Language] = append(langMap[user.Language], user)
|
||||
@@ -54,6 +53,9 @@ func SendRepoTransferNotifyMail(ctx context.Context, doer, newOwner *user_model.
|
||||
return nil
|
||||
}
|
||||
|
||||
if newOwner.IsTypeBot() {
|
||||
return nil
|
||||
}
|
||||
return sendRepoTransferNotifyMailPerLang(newOwner.Language, newOwner, doer, []*user_model.User{newOwner}, repo)
|
||||
}
|
||||
|
||||
@@ -98,7 +100,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
|
||||
|
||||
// SendCollaboratorMail sends mail notification to new collaborator.
|
||||
func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository) {
|
||||
if setting.MailService == nil || !u.IsActive {
|
||||
if setting.MailService == nil || !u.IsMailable() {
|
||||
return
|
||||
}
|
||||
locale := translation.NewLocale(u.Language)
|
||||
|
||||
@@ -168,6 +168,28 @@ func TestMailMentionsComment(t *testing.T) {
|
||||
assert.Equal(t, 3, mails)
|
||||
}
|
||||
|
||||
func TestMailsSkipBots(t *testing.T) {
|
||||
doer, repo, issue, comment := prepareMailerTest(t)
|
||||
comment.Poster = doer
|
||||
var recipients []string
|
||||
defer test.MockVariableValue(&SendAsync, func(msgs ...*sender_service.Message) {
|
||||
for _, msg := range msgs {
|
||||
recipients = append(recipients, msg.To)
|
||||
}
|
||||
})()
|
||||
|
||||
require.NoError(t, user_model.UpdateUserCols(t.Context(), &user_model.User{ID: 5, Type: user_model.UserTypeBot}, "type"))
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
bot := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
require.NoError(t, SendIssueAssignedMail(t.Context(), issue, doer, "", comment, []*user_model.User{user, bot}))
|
||||
require.NoError(t, MailParticipantsComment(t.Context(), comment, activities_model.ActionCommentIssue, issue, []*user_model.User{bot}))
|
||||
require.NoError(t, SendRepoTransferNotifyMail(t.Context(), doer, bot, repo))
|
||||
SendCollaboratorMail(bot, doer, repo)
|
||||
SendRegisterNotifyMail(bot)
|
||||
assert.Contains(t, recipients, user.Email)
|
||||
assert.NotContains(t, strings.Join(recipients, " "), bot.Email)
|
||||
}
|
||||
|
||||
func TestComposeIssueMessage(t *testing.T) {
|
||||
doer, _, issue, _ := prepareMailerTest(t)
|
||||
|
||||
|
||||
@@ -101,8 +101,7 @@ func SendActivateEmailMail(u *user_model.User, email string) {
|
||||
|
||||
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
|
||||
func SendRegisterNotifyMail(u *user_model.User) {
|
||||
if setting.MailService == nil || !u.IsActive {
|
||||
// No mail service configured OR user is inactive
|
||||
if setting.MailService == nil || !u.IsMailable() {
|
||||
return
|
||||
}
|
||||
locale := translation.NewLocale(u.Language)
|
||||
|
||||
Reference in New Issue
Block a user