Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repository transfer has to be confirmed, if user can not create repo for new owner #14792

Merged
merged 122 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from 121 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
86bd92c
add migrations
adelowo Jan 12, 2019
a904524
save to repo_transfer and don't actually perform a transfer just yet
adelowo Jan 12, 2019
e5fb931
make sure there can only be one transfer process at any given time
adelowo Jan 12, 2019
f08acee
add status to check to make sure we check for only 'pending' transfers
adelowo Jan 12, 2019
be9b779
start working on cancellation of a repo transfer request
adelowo Jan 13, 2019
771f814
add ability to cancel an ongoing transfer
adelowo Jan 13, 2019
ace5062
little cleanup
adelowo Jan 15, 2019
ac77491
fix build
adelowo Jan 16, 2019
1c64269
added comments
adelowo Jan 16, 2019
25386e1
fix review
adelowo Jan 24, 2019
02906e2
complete repo transfer
adelowo Feb 9, 2019
2e049da
Merge branch 'master' into repo_transfer
adelowo Mar 24, 2019
a7f7662
start work on email notification
adelowo Mar 24, 2019
ec154fd
cannot transfer an archived repo
adelowo Mar 26, 2019
ec3ece4
fix redirection
adelowo Mar 26, 2019
b515fd6
send email to user
adelowo Mar 26, 2019
79f9952
transfer repository acknowledgement
adelowo Mar 26, 2019
861d9ab
Merge branch 'master' into repo_transfer
adelowo Mar 26, 2019
13984c2
use member of owner team email address for repo transfers to an organ…
adelowo Mar 26, 2019
549aa0c
implement rejection of repo transfer
adelowo Mar 26, 2019
d749dda
fix link
adelowo Mar 26, 2019
5b1b4c4
try to fix tests
adelowo Mar 27, 2019
a29a22e
tests for starting and cancelling a repo transfer
adelowo Mar 27, 2019
7d701c1
fix mispellings
adelowo Mar 27, 2019
a155de7
add repo redirect after a successful repo transfer
adelowo Mar 27, 2019
008f942
use 404 instead of unauthorized
adelowo Mar 27, 2019
1943b82
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Mar 27, 2019
95d6b0b
fix merge conflicts
adelowo Apr 17, 2019
997d530
fix merge conflicts
adelowo Apr 17, 2019
69a62f5
fix build issues
adelowo Apr 17, 2019
b192f03
write notifications
adelowo Apr 19, 2019
8954618
fix godoc
adelowo Apr 19, 2019
4cf79f7
Merge remote-tracking branch 'origin/master' into repo_transfer
adelowo Apr 19, 2019
79f7ae4
Merge branch 'master' into repo_transfer
adelowo Apr 21, 2019
a5eb37c
Merge branch 'master' into repo_transfer
adelowo Apr 26, 2019
e093b20
Merge branch 'master' into repo_transfer
adelowo Apr 28, 2019
b7a5dc4
Merge branch 'master' into repo_transfer
adelowo May 4, 2019
b04ace2
Merge remote-tracking branch 'origin' into repo_transfer
adelowo Jan 26, 2020
efbbf45
fix merge conflicts
adelowo Jan 30, 2020
3a8dcf1
Merge remote-tracking branch 'origin' into repo_transfer
adelowo Feb 2, 2020
7e961ce
fix merge conflicts
adelowo Feb 3, 2020
736dcb5
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 3, 2020
f339291
fix transfer logic and hook email up
adelowo Feb 4, 2020
bba603e
switch transfer logic to services/repo
adelowo Feb 4, 2020
5c578fc
make repository transfer as accepted when user accepts
adelowo Feb 4, 2020
e7501ed
fix test
adelowo Feb 4, 2020
557b353
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 4, 2020
4dff694
fix ci
adelowo Feb 4, 2020
e870ec0
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 7, 2020
5fe80cb
Merge remote-tracking branch 'origin' into repo_transfer
adelowo Feb 16, 2020
dd29d2f
add support for teams ID
adelowo Feb 16, 2020
f43d9a4
Add omit tag
adelowo Feb 16, 2020
e179100
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 16, 2020
b481a5a
update swagger
adelowo Feb 16, 2020
2cd9093
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 16, 2020
8d8bb35
fix test
adelowo Feb 16, 2020
114bc96
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 16, 2020
57336cc
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 16, 2020
4084c40
Fix swagger definition
adelowo Feb 16, 2020
bfd1bbd
Merge branch 'master' into repo_transfer
lafriks Feb 17, 2020
90578de
update migration
adelowo Feb 17, 2020
727dd45
Merge branch 'master' into repo_transfer
adelowo Feb 17, 2020
0bab4a3
Merge branch 'master' of github.com:go-gitea/gitea into repo_transfer
adelowo Feb 17, 2020
85c397f
Merge remote-tracking branch 'origin/master' into repo_transfer
adelowo Apr 2, 2020
9a281a2
Merge branch 'master' into repo_transfer
6543 Feb 24, 2021
140d8a7
clean
6543 Feb 24, 2021
9595bcc
adapt newest changes
6543 Feb 24, 2021
0e3f44b
Check if Tepo is ready for transfer & transfer directly if allowed
6543 Feb 25, 2021
3641959
exec StartRepositoryTransfer within a db session
6543 Feb 25, 2021
6fa25c0
just delete RepositoryTransfer entry if done
6543 Feb 25, 2021
3d541d3
rm unused & fix migration
6543 Feb 25, 2021
0dbeb87
API, dates, tests
6543 Feb 25, 2021
4c409ed
remove TransferStatus
6543 Feb 25, 2021
0d0fb26
...
6543 Feb 25, 2021
fc27a9e
rm migration dep
6543 Feb 25, 2021
87e23c9
rm
6543 Feb 25, 2021
bb65753
fix fixtures
6543 Feb 25, 2021
f4b007c
fix & co
6543 Feb 25, 2021
31712db
rename & add unrelated rm back
6543 Feb 25, 2021
f4dd418
fix deadlock
6543 Feb 25, 2021
3b181e2
Fix Tests & handle RepoExist err
6543 Feb 25, 2021
c8019ec
dont save if RepoTransfer exist in extra var for templates & more
6543 Feb 25, 2021
8640c56
rm templates notification_div.tmpl change
6543 Feb 25, 2021
ba96de1
re-enable transfer repo back from org to user account
6543 Feb 26, 2021
c64b687
Merge branch 'master' into repo_transfer
6543 Feb 26, 2021
390ab2a
nit
6543 Feb 26, 2021
db2b1ea
add test case for latest bugfix
6543 Feb 26, 2021
9f6c7c1
do it the right way!
6543 Feb 26, 2021
0604dae
rework model.CreateRepoTransferNotification()
6543 Feb 26, 2021
21f1e42
mv SendRepoTransferNotifyMail call into right place & rework SendRepo…
6543 Feb 26, 2021
1c9bdcf
lint
6543 Feb 26, 2021
de3247a
rm unrelated diff
6543 Feb 26, 2021
805c257
simplify
6543 Feb 26, 2021
6089ae1
UI test 1
6543 Feb 26, 2021
3119e89
update repoStatus if finaly transfer
6543 Feb 26, 2021
2c9ea8e
has to be more explicite to work ...
6543 Feb 26, 2021
7b44967
imprufe UI
6543 Feb 26, 2021
e3b3258
localize
6543 Feb 26, 2021
e2c1aea
finish UI
6543 Feb 26, 2021
97e447e
Tell user the reason, the button do not work
6543 Feb 26, 2021
97f5b13
sort locale
6543 Feb 26, 2021
a10c15b
finish GetUsersWhoCanCreateOrgRepo()
6543 Feb 26, 2021
ac0d28c
use notification module
6543 Feb 26, 2021
f17bfa8
finish mail
6543 Feb 26, 2021
17f4750
... now
6543 Feb 26, 2021
93d4c0a
update Copyright dates
6543 Feb 26, 2021
6dc23d9
Make UI work for NotificationSourceRepository
6543 Feb 26, 2021
150c36d
Set repo notification-status read if unread
6543 Feb 26, 2021
8ef58b8
fix setRepoNotificationStatusReadIfUnread query
6543 Feb 26, 2021
9e62e8e
Merge branch 'master' into repo_transfer
6543 Feb 27, 2021
eba4d30
Merge branch 'master' into repo_transfer
6543 Feb 27, 2021
5dc7c1a
Merge branch 'master' into repo_transfer
6543 Feb 27, 2021
b3056a0
Apply suggestions from code review
6543 Feb 27, 2021
1387a74
limit var scope if posible
6543 Feb 27, 2021
1bed3c5
line indenting
6543 Feb 27, 2021
77f7094
Merge branch 'master' into repo_transfer
6543 Feb 28, 2021
39f5cd5
engine version of GetUsersWhoCanCreateOrgRepo
6543 Feb 28, 2021
892f43b
Merge branch 'master' into repo_transfer
6543 Feb 28, 2021
8937ad7
Merge branch 'master' into repo_transfer
6543 Feb 28, 2021
d2deb67
Merge branch 'master' into repo_transfer
6543 Feb 28, 2021
c78a409
fix defer sess.Close()
6543 Mar 1, 2021
ba0357a
Merge branch 'master' into repo_transfer
6543 Mar 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions integrations/api_repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,12 +444,22 @@ func TestAPIRepoTransfer(t *testing.T) {
teams *[]int64
expectedStatus int
}{
{ctxUserID: 1, newOwner: "user2", teams: nil, expectedStatus: http.StatusAccepted},
{ctxUserID: 2, newOwner: "user1", teams: nil, expectedStatus: http.StatusAccepted},
{ctxUserID: 2, newOwner: "user6", teams: nil, expectedStatus: http.StatusForbidden},
{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3"

// Transfer to a user with teams in another org should fail
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
// Transfer to a user with non-existent team IDs should fail
{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
// Transfer should go through
{ctxUserID: 1, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
// Let user transfer it back to himself
{ctxUserID: 2, newOwner: "user2", expectedStatus: http.StatusAccepted},
// And revert transfer
{ctxUserID: 2, newOwner: "user3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
// Cannot start transfer to an existing repo
{ctxUserID: 2, newOwner: "user3", teams: nil, expectedStatus: http.StatusUnprocessableEntity},
// Start transfer, repo is now in pending transfer mode
{ctxUserID: 2, newOwner: "user6", teams: nil, expectedStatus: http.StatusCreated},
}

defer prepareTestEnv(t)()
Expand Down
34 changes: 34 additions & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,40 @@ func (err ErrRepoNotExist) Error() string {
err.ID, err.UID, err.OwnerName, err.Name)
}

// ErrNoPendingRepoTransfer is an error type for repositories without a pending
// transfer request
type ErrNoPendingRepoTransfer struct {
RepoID int64
}

func (e ErrNoPendingRepoTransfer) Error() string {
return fmt.Sprintf("repository doesn't have a pending transfer [repo_id: %d]", e.RepoID)
}

// IsErrNoPendingTransfer is an error type when a repository has no pending
// transfers
func IsErrNoPendingTransfer(err error) bool {
_, ok := err.(ErrNoPendingRepoTransfer)
return ok
}

// ErrRepoTransferInProgress represents the state of a repository that has an
// ongoing transfer
type ErrRepoTransferInProgress struct {
Uname string
Name string
}

// IsErrRepoTransferInProgress checks if an error is a ErrRepoTransferInProgress.
func IsErrRepoTransferInProgress(err error) bool {
_, ok := err.(ErrRepoTransferInProgress)
return ok
}

func (err ErrRepoTransferInProgress) Error() string {
return fmt.Sprintf("repository is already being transferred [uname: %s, name: %s]", err.Uname, err.Name)
}

// ErrRepoAlreadyExist represents a "RepoAlreadyExist" kind of error.
type ErrRepoAlreadyExist struct {
Uname string
Expand Down
7 changes: 7 additions & 0 deletions models/fixtures/repo_transfer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-
id: 1
doer_id: 3
recipient_id: 1
repo_id: 3
created_unix: 1553610671
updated_unix: 1553610671
2 changes: 1 addition & 1 deletion models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ func (issue *Issue) ReadBy(userID int64) error {
return err
}

return setNotificationStatusReadIfUnread(x, userID, issue.ID)
return setIssueNotificationStatusReadIfUnread(x, userID, issue.ID)
}

func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ var migrations = []Migration{
NewMigration("Add sessions table for go-chi/session", addSessionTable),
// v173 -> v174
NewMigration("Add time_id column to Comment", addTimeIDCommentColumn),
// v174 -> v175
NewMigration("create repo transfer table", addRepoTransfer),
}

// GetCurrentDBVersion returns the current db version
Expand Down
23 changes: 23 additions & 0 deletions models/migrations/v174.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"xorm.io/xorm"
)

func addRepoTransfer(x *xorm.Engine) error {
type RepoTransfer struct {
ID int64 `xorm:"pk autoincr"`
DoerID int64
RecipientID int64
RepoID int64
TeamIDs []int64
CreatedUnix int64 `xorm:"INDEX NOT NULL created"`
UpdatedUnix int64 `xorm:"INDEX NOT NULL updated"`
}

return x.Sync(new(RepoTransfer))
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func init() {
new(ProjectBoard),
new(ProjectIssue),
new(Session),
new(RepoTransfer),
)

gonicNames := []string{"SSL", "UID"}
Expand Down
78 changes: 70 additions & 8 deletions models/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const (
NotificationSourcePullRequest
// NotificationSourceCommit is a notification of a commit
NotificationSourceCommit
// NotificationSourceRepository is a notification for a repository
NotificationSourceRepository
)

// Notification represents a notification
Expand Down Expand Up @@ -119,6 +121,46 @@ func GetNotifications(opts FindNotificationOptions) (NotificationList, error) {
return getNotifications(x, opts)
}

// CreateRepoTransferNotification creates notification for the user a repository was transferred to
func CreateRepoTransferNotification(doer, newOwner *User, repo *Repository) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
var notify []*Notification

if newOwner.IsOrganization() {
users, err := getUsersWhoCanCreateOrgRepo(sess, newOwner.ID)
if err != nil || len(users) == 0 {
return err
}
for i := range users {
notify = append(notify, &Notification{
UserID: users[i].ID,
RepoID: repo.ID,
Status: NotificationStatusUnread,
UpdatedBy: doer.ID,
Source: NotificationSourceRepository,
})
}
} else {
notify = []*Notification{{
UserID: newOwner.ID,
RepoID: repo.ID,
Status: NotificationStatusUnread,
UpdatedBy: doer.ID,
Source: NotificationSourceRepository,
}}
}

if _, err := sess.InsertMulti(notify); err != nil {
return err
}

return sess.Commit()
}

// CreateOrUpdateIssueNotifications creates an issue notification
// for each watcher, or updates it if already exists
// receiverID > 0 just send to reciver, else send to all watcher
Expand Down Expand Up @@ -363,7 +405,7 @@ func (n *Notification) loadRepo(e Engine) (err error) {
}

func (n *Notification) loadIssue(e Engine) (err error) {
if n.Issue == nil {
if n.Issue == nil && n.IssueID != 0 {
n.Issue, err = getIssueByID(e, n.IssueID)
if err != nil {
return fmt.Errorf("getIssueByID [%d]: %v", n.IssueID, err)
Expand All @@ -374,7 +416,7 @@ func (n *Notification) loadIssue(e Engine) (err error) {
}

func (n *Notification) loadComment(e Engine) (err error) {
if n.Comment == nil && n.CommentID > 0 {
if n.Comment == nil && n.CommentID != 0 {
n.Comment, err = getCommentByID(e, n.CommentID)
if err != nil {
return fmt.Errorf("GetCommentByID [%d] for issue ID [%d]: %v", n.CommentID, n.IssueID, err)
Expand Down Expand Up @@ -405,10 +447,18 @@ func (n *Notification) GetIssue() (*Issue, error) {

// HTMLURL formats a URL-string to the notification
func (n *Notification) HTMLURL() string {
if n.Comment != nil {
return n.Comment.HTMLURL()
switch n.Source {
case NotificationSourceIssue, NotificationSourcePullRequest:
if n.Comment != nil {
return n.Comment.HTMLURL()
}
return n.Issue.HTMLURL()
case NotificationSourceCommit:
return n.Repository.HTMLURL() + "/commit/" + n.CommitID
case NotificationSourceRepository:
return n.Repository.HTMLURL()
}
return n.Issue.HTMLURL()
return ""
}

// APIURL formats a URL-string to the notification
Expand Down Expand Up @@ -562,8 +612,10 @@ func (nl NotificationList) LoadIssues() ([]int, error) {
if notification.Issue == nil {
notification.Issue = issues[notification.IssueID]
if notification.Issue == nil {
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
failures = append(failures, i)
if notification.IssueID != 0 {
log.Error("Notification[%d]: IssueID: %d Not Found", notification.ID, notification.IssueID)
failures = append(failures, i)
}
continue
}
notification.Issue.Repo = notification.Repository
Expand Down Expand Up @@ -683,7 +735,7 @@ func GetUIDsAndNotificationCounts(since, until timeutil.TimeStamp) ([]UserIDCoun
return res, x.SQL(sql, since, until, NotificationStatusUnread).Find(&res)
}

func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
func setIssueNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
notification, err := getIssueNotification(e, userID, issueID)
// ignore if not exists
if err != nil {
Expand All @@ -700,6 +752,16 @@ func setNotificationStatusReadIfUnread(e Engine, userID, issueID int64) error {
return err
}

func setRepoNotificationStatusReadIfUnread(e Engine, userID, repoID int64) error {
_, err := e.Where(builder.Eq{
"user_id": userID,
"status": NotificationStatusUnread,
"source": NotificationSourceRepository,
"repo_id": repoID,
}).Cols("status").Update(&Notification{Status: NotificationStatusRead})
return err
}

// SetNotificationStatus change the notification status
func SetNotificationStatus(notificationID int64, user *User, status NotificationStatus) error {
notification, err := getNotificationByID(x, notificationID)
Expand Down
14 changes: 14 additions & 0 deletions models/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,20 @@ func CanCreateOrgRepo(orgID, uid int64) (bool, error) {
Exist(new(Team))
}

// GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization
func GetUsersWhoCanCreateOrgRepo(orgID int64) ([]*User, error) {
zeripath marked this conversation as resolved.
Show resolved Hide resolved
return getUsersWhoCanCreateOrgRepo(x, orgID)
}

func getUsersWhoCanCreateOrgRepo(e Engine, orgID int64) ([]*User, error) {
users := make([]*User, 0, 10)
return users, x.
Join("INNER", "`team_user`", "`team_user`.uid=`user`.id").
Join("INNER", "`team`", "`team`.id=`team_user`.team_id").
Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": AccessModeOwner})).
And("team_user.org_id = ?", orgID).Asc("`user`.name").Find(&users)
}

func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) {
orgs := make([]*User, 0, 10)
if !showAll {
Expand Down
18 changes: 18 additions & 0 deletions models/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,3 +635,21 @@ func TestHasOrgVisibleTypePrivate(t *testing.T) {
assert.Equal(t, test2, false) // user not a part of org
assert.Equal(t, test3, false) // logged out user
}

func TestGetUsersWhoCanCreateOrgRepo(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

users, err := GetUsersWhoCanCreateOrgRepo(3)
assert.NoError(t, err)
assert.Len(t, users, 2)
var ids []int64
for i := range users {
ids = append(ids, users[i].ID)
}
assert.ElementsMatch(t, ids, []int64{2, 28})

users, err = GetUsersWhoCanCreateOrgRepo(7)
assert.NoError(t, err)
assert.Len(t, users, 1)
assert.EqualValues(t, 5, users[0].ID)
}
Loading