diff --git a/modules/repository/collaborator.go b/modules/repository/collaborator.go index f5cdc35045..4099a178f2 100644 --- a/modules/repository/collaborator.go +++ b/modules/repository/collaborator.go @@ -14,6 +14,10 @@ import ( ) func AddCollaborator(ctx context.Context, repo *repo_model.Repository, u *user_model.User) error { + if user_model.IsBlocked(ctx, repo.OwnerID, u.ID) || user_model.IsBlocked(ctx, u.ID, repo.OwnerID) { + return user_model.ErrBlockedByUser + } + return db.WithTx(ctx, func(ctx context.Context) error { collaboration := &repo_model.Collaboration{ RepoID: repo.ID, diff --git a/modules/repository/collaborator_test.go b/modules/repository/collaborator_test.go index c772806819..b051aa2ac7 100644 --- a/modules/repository/collaborator_test.go +++ b/modules/repository/collaborator_test.go @@ -33,6 +33,33 @@ func TestRepository_AddCollaborator(t *testing.T) { testSuccess(3, 4) } +func TestRepository_AddCollaborator_IsBlocked(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(repoID, userID int64) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) + assert.NoError(t, repo.LoadOwner(db.DefaultContext)) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) + + // Owner blocked user. + unittest.AssertSuccessfulInsert(t, &user_model.BlockedUser{UserID: repo.OwnerID, BlockID: userID}) + assert.ErrorIs(t, AddCollaborator(db.DefaultContext, repo, user), user_model.ErrBlockedByUser) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}, &user_model.User{ID: userID}) + _, err := db.DeleteByBean(db.DefaultContext, &user_model.BlockedUser{UserID: repo.OwnerID, BlockID: userID}) + assert.NoError(t, err) + + // User has owner blocked. + unittest.AssertSuccessfulInsert(t, &user_model.BlockedUser{UserID: userID, BlockID: repo.OwnerID}) + assert.ErrorIs(t, AddCollaborator(db.DefaultContext, repo, user), user_model.ErrBlockedByUser) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repoID}, &user_model.User{ID: userID}) + } + // Ensure idempotency (public repository). + testSuccess(1, 4) + testSuccess(1, 4) + // Add collaborator to private repository. + testSuccess(3, 4) +} + func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 997169632a..6bc9b5b7d3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -607,6 +607,7 @@ block_user = Block User block_user.detail = Please understand that if you block this user, other actions will be taken. Such as: block_user.detail_1 = You are being unfollowed from this user. block_user.detail_2 = This user cannot interact with your repositories, created issues and comments. +block_user.detail_3 = This user cannot add you as a collaborator, nor can you add them as a collaborator. follow_blocked_user = You cannot follow this user because you have blocked this user or this user has blocked you. form.name_reserved = The username "%s" is reserved. @@ -2080,6 +2081,8 @@ settings.add_collaborator_success = The collaborator has been added. settings.add_collaborator_inactive_user = Cannot add an inactive user as a collaborator. settings.add_collaborator_owner = Cannot add an owner as a collaborator. settings.add_collaborator_duplicate = The collaborator is already added to this repository. +settings.add_collaborator_blocked_our = Cannot add the collaborator, because the repository owner has blocked them. +settings.add_collaborator_blocked_them = Cannot add the collaborator, because they have blocked the repository owner. settings.delete_collaborator = Remove settings.collaborator_deletion = Remove Collaborator settings.collaborator_deletion_desc = Removing a collaborator will revoke their access to this repository. Continue? diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 942d4c799f..cfa45e9995 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -156,6 +156,8 @@ func AddCollaborator(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "422": // "$ref": "#/responses/validationError" + // "403": + // "$ref": "#/responses/forbidden" form := web.GetForm(ctx).(*api.AddCollaboratorOption) @@ -175,7 +177,11 @@ func AddCollaborator(ctx *context.APIContext) { } if err := repo_module.AddCollaborator(ctx, ctx.Repo.Repository, collaborator); err != nil { - ctx.Error(http.StatusInternalServerError, "AddCollaborator", err) + if errors.Is(err, user_model.ErrBlockedByUser) { + ctx.Error(http.StatusForbidden, "AddCollaborator", err) + } else { + ctx.Error(http.StatusInternalServerError, "AddCollaborator", err) + } return } diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index 8f2d306862..a9dc47693d 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -4,6 +4,7 @@ package setting import ( + "errors" "net/http" "strings" @@ -102,7 +103,18 @@ func CollaborationPost(ctx *context.Context) { } if err = repo_module.AddCollaborator(ctx, ctx.Repo.Repository, u); err != nil { - ctx.ServerError("AddCollaborator", err) + if !errors.Is(err, user_model.ErrBlockedByUser) { + ctx.ServerError("AddCollaborator", err) + return + } + + // To give an good error message, be precise on who has blocked who. + if blockedOurs := user_model.IsBlocked(ctx, ctx.Repo.Repository.OwnerID, u.ID); blockedOurs { + ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_blocked_our")) + } else { + ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_blocked_them")) + } + ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") return } diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go index 6f7c844ce7..46180ac372 100644 --- a/routers/web/repo/setting/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -102,13 +102,15 @@ func TestCollaborationPost(t *testing.T) { ctx.Req.Form.Set("collaborator", "user4") u := &user_model.User{ + ID: 2, LowerName: "user2", Type: user_model.UserTypeIndividual, } re := &repo_model.Repository{ - ID: 2, - Owner: u, + ID: 2, + Owner: u, + OwnerID: u.ID, } repo := &context.Repository{ @@ -160,13 +162,15 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { ctx.Req.Form.Set("collaborator", "user4") u := &user_model.User{ + ID: 2, LowerName: "user2", Type: user_model.UserTypeIndividual, } re := &repo_model.Repository{ - ID: 2, - Owner: u, + ID: 2, + Owner: u, + OwnerID: u.ID, } repo := &context.Repository{ diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 6f27cd9c76..eefe3b2c3d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3948,6 +3948,9 @@ "204": { "$ref": "#/responses/empty" }, + "403": { + "$ref": "#/responses/forbidden" + }, "422": { "$ref": "#/responses/validationError" } diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 6a2f719a34..05e95a1a34 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -52,6 +52,7 @@