mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Restrict permission check on repositories and fix some problems (#5314)
* fix units permission problems * fix some bugs and merge LoadUnits to repoAssignment * refactor permission struct and add some copyright heads * remove unused codes * fix routes units check * improve permission check * add unit tests for permission * fix typo * fix tests * fix some routes * fix api permission check * improve permission check * fix some permission check * fix tests * fix tests * improve some permission check * fix some permission check * refactor AccessLevel * fix bug * fix tests * fix tests * fix tests * fix AccessLevel * rename CanAccess * fix tests * fix comment * fix bug * add missing unit for test repos * fix bug * rename some functions * fix routes check
This commit is contained in:
		
							
								
								
									
										14
									
								
								cmd/serv.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								cmd/serv.go
									
									
									
									
									
								
							| @@ -193,7 +193,7 @@ func runServ(c *cli.Context) error { | |||||||
| 		keyID int64 | 		keyID int64 | ||||||
| 		user  *models.User | 		user  *models.User | ||||||
| 	) | 	) | ||||||
| 	if requestedMode == models.AccessModeWrite || repo.IsPrivate { | 	if requestedMode == models.AccessModeWrite || repo.IsPrivate || setting.Service.RequireSignInView { | ||||||
| 		keys := strings.Split(c.Args()[0], "-") | 		keys := strings.Split(c.Args()[0], "-") | ||||||
| 		if len(keys) != 2 { | 		if len(keys) != 2 { | ||||||
| 			fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) | 			fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) | ||||||
| @@ -236,7 +236,7 @@ func runServ(c *cli.Context) error { | |||||||
| 					user.Name, repoPath) | 					user.Name, repoPath) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			mode, err := private.AccessLevel(user.ID, repo.ID) | 			mode, err := private.CheckUnitUser(user.ID, repo.ID, user.IsAdmin, unitType) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				fail("Internal error", "Failed to check access: %v", err) | 				fail("Internal error", "Failed to check access: %v", err) | ||||||
| 			} else if *mode < requestedMode { | 			} else if *mode < requestedMode { | ||||||
| @@ -249,16 +249,6 @@ func runServ(c *cli.Context) error { | |||||||
| 					user.Name, requestedMode, repoPath) | 					user.Name, requestedMode, repoPath) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			check, err := private.CheckUnitUser(user.ID, repo.ID, user.IsAdmin, unitType) |  | ||||||
| 			if err != nil { |  | ||||||
| 				fail("You do not have allowed for this action", "Failed to access internal api: [user.Name: %s, repoPath: %s]", user.Name, repoPath) |  | ||||||
| 			} |  | ||||||
| 			if !check { |  | ||||||
| 				fail("You do not have allowed for this action", |  | ||||||
| 					"User %s does not have allowed access to repository %s 's code", |  | ||||||
| 					user.Name, repoPath) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			os.Setenv(models.EnvPusherName, user.Name) | 			os.Setenv(models.EnvPusherName, user.Name) | ||||||
| 			os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID)) | 			os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID)) | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -164,7 +164,7 @@ func TestAPISearchRepo(t *testing.T) { | |||||||
| 					assert.Len(t, body.Data, expected.count) | 					assert.Len(t, body.Data, expected.count) | ||||||
| 					for _, repo := range body.Data { | 					for _, repo := range body.Data { | ||||||
| 						r := getRepo(t, repo.ID) | 						r := getRepo(t, repo.ID) | ||||||
| 						hasAccess, err := models.HasAccess(userID, r, models.AccessModeRead) | 						hasAccess, err := models.HasAccess(userID, r) | ||||||
| 						assert.NoError(t, err) | 						assert.NoError(t, err) | ||||||
| 						assert.True(t, hasAccess) | 						assert.True(t, hasAccess) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -80,22 +80,6 @@ func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) { | |||||||
| 	return a.Mode, nil | 	return a.Mode, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the |  | ||||||
| // user does not have access. |  | ||||||
| func AccessLevel(userID int64, repo *Repository) (AccessMode, error) { |  | ||||||
| 	return accessLevel(x, userID, repo) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) { |  | ||||||
| 	mode, err := accessLevel(e, userID, repo) |  | ||||||
| 	return testMode <= mode, err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // HasAccess returns true if user has access to repo |  | ||||||
| func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) { |  | ||||||
| 	return hasAccess(x, userID, repo, testMode) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type repoAccess struct { | type repoAccess struct { | ||||||
| 	Access     `xorm:"extends"` | 	Access     `xorm:"extends"` | ||||||
| 	Repository `xorm:"extends"` | 	Repository `xorm:"extends"` | ||||||
|   | |||||||
| @@ -20,28 +20,28 @@ var accessModes = []AccessMode{ | |||||||
| func TestAccessLevel(t *testing.T) { | func TestAccessLevel(t *testing.T) { | ||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
| 	user1 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | 	user2 := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
| 	user2 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | 	user5 := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||||
| 	// A public repository owned by User 2 | 	// A public repository owned by User 2 | ||||||
| 	repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | 	repo1 := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository) | ||||||
| 	assert.False(t, repo1.IsPrivate) | 	assert.False(t, repo1.IsPrivate) | ||||||
| 	// A private repository owned by Org 3 | 	// A private repository owned by Org 3 | ||||||
| 	repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | 	repo3 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | ||||||
| 	assert.True(t, repo2.IsPrivate) | 	assert.True(t, repo3.IsPrivate) | ||||||
|  |  | ||||||
| 	level, err := AccessLevel(user1.ID, repo1) | 	level, err := AccessLevel(user2, repo1) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, AccessModeOwner, level) | 	assert.Equal(t, AccessModeOwner, level) | ||||||
|  |  | ||||||
| 	level, err = AccessLevel(user1.ID, repo2) | 	level, err = AccessLevel(user2, repo3) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, AccessModeWrite, level) | 	assert.Equal(t, AccessModeOwner, level) | ||||||
|  |  | ||||||
| 	level, err = AccessLevel(user2.ID, repo1) | 	level, err = AccessLevel(user5, repo1) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, AccessModeRead, level) | 	assert.Equal(t, AccessModeRead, level) | ||||||
|  |  | ||||||
| 	level, err = AccessLevel(user2.ID, repo2) | 	level, err = AccessLevel(user5, repo3) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, AccessModeNone, level) | 	assert.Equal(t, AccessModeNone, level) | ||||||
| } | } | ||||||
| @@ -58,23 +58,18 @@ func TestHasAccess(t *testing.T) { | |||||||
| 	repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | 	repo2 := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | ||||||
| 	assert.True(t, repo2.IsPrivate) | 	assert.True(t, repo2.IsPrivate) | ||||||
|  |  | ||||||
| 	for _, accessMode := range accessModes { | 	has, err := HasAccess(user1.ID, repo1) | ||||||
| 		has, err := HasAccess(user1.ID, repo1, accessMode) |  | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.True(t, has) | 	assert.True(t, has) | ||||||
|  |  | ||||||
| 		has, err = HasAccess(user1.ID, repo2, accessMode) | 	has, err = HasAccess(user1.ID, repo2) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 		assert.Equal(t, accessMode <= AccessModeWrite, has) |  | ||||||
|  |  | ||||||
| 		has, err = HasAccess(user2.ID, repo1, accessMode) | 	has, err = HasAccess(user2.ID, repo1) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 		assert.Equal(t, accessMode <= AccessModeRead, has) |  | ||||||
|  |  | ||||||
| 		has, err = HasAccess(user2.ID, repo2, accessMode) | 	has, err = HasAccess(user2.ID, repo2) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 		assert.Equal(t, accessMode <= AccessModeNone, has) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestUser_GetRepositoryAccesses(t *testing.T) { | func TestUser_GetRepositoryAccesses(t *testing.T) { | ||||||
|   | |||||||
| @@ -243,10 +243,16 @@ func updateUserWhitelist(repo *Repository, currentWhitelist, newWhitelist []int6 | |||||||
|  |  | ||||||
| 	whitelist = make([]int64, 0, len(newWhitelist)) | 	whitelist = make([]int64, 0, len(newWhitelist)) | ||||||
| 	for _, userID := range newWhitelist { | 	for _, userID := range newWhitelist { | ||||||
| 		has, err := hasAccess(x, userID, repo, AccessModeWrite) | 		user, err := GetUserByID(userID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, fmt.Errorf("HasAccess [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) | 			return nil, fmt.Errorf("GetUserByID [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) | ||||||
| 		} else if !has { | 		} | ||||||
|  | 		perm, err := GetUserRepoPermission(repo, user) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("GetUserRepoPermission [user_id: %d, repo_id: %d]: %v", userID, repo.ID, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !perm.CanWrite(UnitTypeCode) { | ||||||
| 			continue // Drop invalid user ID | 			continue // Drop invalid user ID | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -109,3 +109,115 @@ | |||||||
|   type: 5 |   type: 5 | ||||||
|   config: "{}" |   config: "{}" | ||||||
|   created_unix: 1535593231 |   created_unix: 1535593231 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 17 | ||||||
|  |   repo_id: 4 | ||||||
|  |   type: 4 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 18 | ||||||
|  |   repo_id: 4 | ||||||
|  |   type: 5 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 19 | ||||||
|  |   repo_id: 4 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 20 | ||||||
|  |   repo_id: 4 | ||||||
|  |   type: 2 | ||||||
|  |   config: "{\"EnableTimetracker\":true,\"AllowOnlyContributorsToTrackTime\":true}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 21 | ||||||
|  |   repo_id: 4 | ||||||
|  |   type: 3 | ||||||
|  |   config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowSquash\":true}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 22 | ||||||
|  |   repo_id: 2 | ||||||
|  |   type: 4 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 23 | ||||||
|  |   repo_id: 2 | ||||||
|  |   type: 5 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 24 | ||||||
|  |   repo_id: 2 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 946684810 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 25 | ||||||
|  |   repo_id: 32 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 26 | ||||||
|  |   repo_id: 32 | ||||||
|  |   type: 2 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 27 | ||||||
|  |   repo_id: 24 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 28 | ||||||
|  |   repo_id: 24 | ||||||
|  |   type: 2 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 29 | ||||||
|  |   repo_id: 16 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 30 | ||||||
|  |   repo_id: 23 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 31 | ||||||
|  |   repo_id: 27 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 32 | ||||||
|  |   repo_id: 28 | ||||||
|  |   type: 1 | ||||||
|  |   config: "{}" | ||||||
|  |   created_unix: 1524304355 | ||||||
| @@ -391,7 +391,7 @@ | |||||||
|   is_mirror: false |   is_mirror: false | ||||||
|  |  | ||||||
| - | - | ||||||
|   id: 32 |   id: 32 # org public repo | ||||||
|   owner_id: 3 |   owner_id: 3 | ||||||
|   lower_name: repo21 |   lower_name: repo21 | ||||||
|   name: repo21 |   name: repo21 | ||||||
|   | |||||||
| @@ -51,3 +51,30 @@ | |||||||
|   authorize: 4 # owner |   authorize: 4 # owner | ||||||
|   num_repos: 2 |   num_repos: 2 | ||||||
|   num_members: 1 |   num_members: 1 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 7 | ||||||
|  |   org_id: 3 | ||||||
|  |   lower_name: test_team | ||||||
|  |   name: test_team | ||||||
|  |   authorize: 2 # write | ||||||
|  |   num_repos: 1 | ||||||
|  |   num_members: 1 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 8 | ||||||
|  |   org_id: 17 | ||||||
|  |   lower_name: test_team | ||||||
|  |   name: test_team | ||||||
|  |   authorize: 2 # write | ||||||
|  |   num_repos: 1 | ||||||
|  |   num_members: 1 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 9 | ||||||
|  |   org_id: 17 | ||||||
|  |   lower_name: review_team | ||||||
|  |   name: review_team | ||||||
|  |   authorize: 1 # read | ||||||
|  |   num_repos: 1 | ||||||
|  |   num_members: 1 | ||||||
| @@ -45,3 +45,21 @@ | |||||||
|   org_id: 3 |   org_id: 3 | ||||||
|   team_id: 1 |   team_id: 1 | ||||||
|   repo_id: 32 |   repo_id: 32 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 9 | ||||||
|  |   org_id: 3 | ||||||
|  |   team_id: 7 | ||||||
|  |   repo_id: 32 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 10 | ||||||
|  |   org_id: 17 | ||||||
|  |   team_id: 8 | ||||||
|  |   repo_id: 24 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 11 | ||||||
|  |   org_id: 17 | ||||||
|  |   team_id: 9 | ||||||
|  |   repo_id: 24 | ||||||
| @@ -207,3 +207,18 @@ | |||||||
|   id: 42 |   id: 42 | ||||||
|   team_id: 6 |   team_id: 6 | ||||||
|   type: 7 |   type: 7 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 43 | ||||||
|  |   team_id: 7 | ||||||
|  |   type: 2 # issues | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 44 | ||||||
|  |   team_id: 8 | ||||||
|  |   type: 2 # issues | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 45 | ||||||
|  |   team_id: 9 | ||||||
|  |   type: 1 # code | ||||||
| @@ -45,3 +45,21 @@ | |||||||
|   org_id: 19 |   org_id: 19 | ||||||
|   team_id: 6 |   team_id: 6 | ||||||
|   uid: 20 |   uid: 20 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 9 | ||||||
|  |   org_id: 3 | ||||||
|  |   team_id: 7 | ||||||
|  |   uid: 15 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 10 | ||||||
|  |   org_id: 17 | ||||||
|  |   team_id: 8 | ||||||
|  |   uid: 2 | ||||||
|  |  | ||||||
|  | - | ||||||
|  |   id: 11 | ||||||
|  |   org_id: 17 | ||||||
|  |   team_id: 9 | ||||||
|  |   uid: 20 | ||||||
| @@ -47,7 +47,7 @@ | |||||||
|   avatar_email: user3@example.com |   avatar_email: user3@example.com | ||||||
|   num_repos: 3 |   num_repos: 3 | ||||||
|   num_members: 2 |   num_members: 2 | ||||||
|   num_teams: 2 |   num_teams: 3 | ||||||
|  |  | ||||||
| - | - | ||||||
|   id: 4 |   id: 4 | ||||||
| @@ -266,7 +266,7 @@ | |||||||
|   num_repos: 2 |   num_repos: 2 | ||||||
|   is_active: true |   is_active: true | ||||||
|   num_members: 2 |   num_members: 2 | ||||||
|   num_teams: 1 |   num_teams: 3 | ||||||
|  |  | ||||||
| - | - | ||||||
|   id: 18 |   id: 18 | ||||||
|   | |||||||
| @@ -385,7 +385,7 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		if err = issue.loadPullRequest(x); err != nil { | 		if err = issue.loadPullRequest(x); err != nil { | ||||||
| 			log.Error(4, "loadPullRequest: %v", err) | 			log.Error(4, "loadPullRequest: %v", err) | ||||||
| @@ -468,9 +468,11 @@ func (issue *Issue) RemoveLabel(doer *User, label *Label) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if has, err := HasAccess(doer.ID, issue.Repo, AccessModeWrite); err != nil { | 	perm, err := GetUserRepoPermission(issue.Repo, doer) | ||||||
|  | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} else if !has { | 	} | ||||||
|  | 	if !perm.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
| 		return ErrLabelNotExist{} | 		return ErrLabelNotExist{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -511,9 +513,11 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if has, err := hasAccess(sess, doer.ID, issue.Repo, AccessModeWrite); err != nil { | 	perm, err := getUserRepoPermission(sess, issue.Repo, doer) | ||||||
|  | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} else if !has { | 	} | ||||||
|  | 	if !perm.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
| 		return ErrLabelNotExist{} | 		return ErrLabelNotExist{} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -529,7 +533,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) { | |||||||
| 		return fmt.Errorf("loadPoster: %v", err) | 		return fmt.Errorf("loadPoster: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		err = issue.PullRequest.LoadIssue() | 		err = issue.PullRequest.LoadIssue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -723,7 +727,7 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e | |||||||
| 	} | 	} | ||||||
| 	sess.Close() | 	sess.Close() | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		// Merge pull request calls issue.changeStatus so we need to handle separately. | 		// Merge pull request calls issue.changeStatus so we need to handle separately. | ||||||
| 		issue.PullRequest.Issue = issue | 		issue.PullRequest.Issue = issue | ||||||
| @@ -785,7 +789,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		issue.PullRequest.Issue = issue | 		issue.PullRequest.Issue = issue | ||||||
| 		err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | 		err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | ||||||
| @@ -851,7 +855,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) { | |||||||
| 		return fmt.Errorf("UpdateIssueCols: %v", err) | 		return fmt.Errorf("UpdateIssueCols: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		issue.PullRequest.Issue = issue | 		issue.PullRequest.Issue = issue | ||||||
| 		err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | 		err = PrepareWebhooks(issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | ||||||
| @@ -946,9 +950,13 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) { | |||||||
| 	// Check for and validate assignees | 	// Check for and validate assignees | ||||||
| 	if len(opts.AssigneeIDs) > 0 { | 	if len(opts.AssigneeIDs) > 0 { | ||||||
| 		for _, assigneeID := range opts.AssigneeIDs { | 		for _, assigneeID := range opts.AssigneeIDs { | ||||||
| 			valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite) | 			user, err := getUserByID(e, assigneeID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err) | 				return fmt.Errorf("getUserByID [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err) | ||||||
|  | 			} | ||||||
|  | 			valid, err := canBeAssigned(e, user, opts.Repo) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("canBeAssigned [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err) | ||||||
| 			} | 			} | ||||||
| 			if !valid { | 			if !valid { | ||||||
| 				return ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: opts.Repo.Name} | 				return ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: opts.Repo.Name} | ||||||
| @@ -1071,7 +1079,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, assigneeIDs []in | |||||||
| 		log.Error(4, "MailParticipants: %v", err) | 		log.Error(4, "MailParticipants: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(issue.Poster.ID, issue.Repo) | 	mode, _ := AccessLevel(issue.Poster, issue.Repo) | ||||||
| 	if err = PrepareWebhooks(repo, HookEventIssues, &api.IssuePayload{ | 	if err = PrepareWebhooks(repo, HookEventIssues, &api.IssuePayload{ | ||||||
| 		Action:     api.HookIssueOpened, | 		Action:     api.HookIssueOpened, | ||||||
| 		Index:      issue.Index, | 		Index:      issue.Index, | ||||||
|   | |||||||
| @@ -159,13 +159,14 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||||||
| 		return fmt.Errorf("createAssigneeComment: %v", err) | 		return fmt.Errorf("createAssigneeComment: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// if issue/pull is in the middle of creation - don't call webhook | 	// if pull request is in the middle of creation - don't call webhook | ||||||
| 	if isCreate { | 	if isCreate { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := accessLevel(sess, doer.ID, issue.Repo) |  | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
|  | 		mode, _ := accessLevelUnit(sess, doer, issue.Repo, UnitTypePullRequests) | ||||||
|  |  | ||||||
| 		if err = issue.loadPullRequest(sess); err != nil { | 		if err = issue.loadPullRequest(sess); err != nil { | ||||||
| 			return fmt.Errorf("loadPullRequest: %v", err) | 			return fmt.Errorf("loadPullRequest: %v", err) | ||||||
| 		} | 		} | ||||||
| @@ -186,6 +187,8 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in | |||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
| 	} else { | 	} else { | ||||||
|  | 		mode, _ := accessLevelUnit(sess, doer, issue.Repo, UnitTypeIssues) | ||||||
|  |  | ||||||
| 		apiIssue := &api.IssuePayload{ | 		apiIssue := &api.IssuePayload{ | ||||||
| 			Index:      issue.Index, | 			Index:      issue.Index, | ||||||
| 			Issue:      issue.APIFormat(), | 			Issue:      issue.APIFormat(), | ||||||
|   | |||||||
| @@ -795,7 +795,7 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri | |||||||
| 		return nil, fmt.Errorf("CreateComment: %v", err) | 		return nil, fmt.Errorf("CreateComment: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(doer.ID, repo) | 	mode, _ := AccessLevel(doer, repo) | ||||||
| 	if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{ | 	if err = PrepareWebhooks(repo, HookEventIssueComment, &api.IssueCommentPayload{ | ||||||
| 		Action:     api.HookIssueCommentCreated, | 		Action:     api.HookIssueCommentCreated, | ||||||
| 		Issue:      issue.APIFormat(), | 		Issue:      issue.APIFormat(), | ||||||
| @@ -990,7 +990,7 @@ func UpdateComment(doer *User, c *Comment, oldContent string) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(doer.ID, c.Issue.Repo) | 	mode, _ := AccessLevel(doer, c.Issue.Repo) | ||||||
| 	if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | 	if err := PrepareWebhooks(c.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | ||||||
| 		Action:  api.HookIssueCommentEdited, | 		Action:  api.HookIssueCommentEdited, | ||||||
| 		Issue:   c.Issue.APIFormat(), | 		Issue:   c.Issue.APIFormat(), | ||||||
| @@ -1047,7 +1047,7 @@ func DeleteComment(doer *User, comment *Comment) error { | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(doer.ID, comment.Issue.Repo) | 	mode, _ := AccessLevel(doer, comment.Issue.Repo) | ||||||
|  |  | ||||||
| 	if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | 	if err := PrepareWebhooks(comment.Issue.Repo, HookEventIssueComment, &api.IssueCommentPayload{ | ||||||
| 		Action:     api.HookIssueCommentDeleted, | 		Action:     api.HookIssueCommentDeleted, | ||||||
|   | |||||||
| @@ -377,7 +377,7 @@ func ChangeMilestoneAssign(issue *Issue, doer *User, oldMilestoneID int64) (err | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(doer.ID, issue.Repo) | 	mode, _ := AccessLevel(doer, issue.Repo) | ||||||
| 	if issue.IsPull { | 	if issue.IsPull { | ||||||
| 		err = issue.PullRequest.LoadIssue() | 		err = issue.PullRequest.LoadIssue() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|   | |||||||
| @@ -139,10 +139,11 @@ func CheckLFSAccessForRepo(u *User, repo *Repository, mode AccessMode) error { | |||||||
| 	if u == nil { | 	if u == nil { | ||||||
| 		return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode} | 		return ErrLFSUnauthorizedAction{repo.ID, "undefined", mode} | ||||||
| 	} | 	} | ||||||
| 	has, err := HasAccess(u.ID, repo, mode) | 	perm, err := GetUserRepoPermission(repo, u) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} else if !has { | 	} | ||||||
|  | 	if !perm.CanAccess(mode, UnitTypeCode) { | ||||||
| 		return ErrLFSUnauthorizedAction{repo.ID, u.DisplayName(), mode} | 		return ErrLFSUnauthorizedAction{repo.ID, u.DisplayName(), mode} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|   | |||||||
| @@ -177,7 +177,7 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e | |||||||
| 		return fmt.Errorf("getTeamUsersByTeamID: %v", err) | 		return fmt.Errorf("getTeamUsersByTeamID: %v", err) | ||||||
| 	} | 	} | ||||||
| 	for _, teamUser := range teamUsers { | 	for _, teamUser := range teamUsers { | ||||||
| 		has, err := hasAccess(e, teamUser.UID, repo, AccessModeRead) | 		has, err := hasAccess(e, teamUser.UID, repo) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} else if has { | 		} else if has { | ||||||
| @@ -434,7 +434,7 @@ func DeleteTeam(t *Team) error { | |||||||
|  |  | ||||||
| 		// Remove watches from all users and now unaccessible repos | 		// Remove watches from all users and now unaccessible repos | ||||||
| 		for _, user := range t.Members { | 		for _, user := range t.Members { | ||||||
| 			has, err := hasAccess(sess, user.ID, repo, AccessModeRead) | 			has, err := hasAccess(sess, user.ID, repo) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} else if has { | 			} else if has { | ||||||
| @@ -652,7 +652,7 @@ func removeTeamMember(e *xorm.Session, team *Team, userID int64) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Remove watches from now unaccessible | 		// Remove watches from now unaccessible | ||||||
| 		has, err := hasAccess(e, userID, repo, AccessModeRead) | 		has, err := hasAccess(e, userID, repo) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} else if has { | 		} else if has { | ||||||
|   | |||||||
| @@ -243,7 +243,7 @@ func TestDeleteTeam(t *testing.T) { | |||||||
| 	// check that team members don't have "leftover" access to repos | 	// check that team members don't have "leftover" access to repos | ||||||
| 	user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | 	user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | ||||||
| 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository) | ||||||
| 	accessMode, err := AccessLevel(user.ID, repo) | 	accessMode, err := AccessLevel(user, repo) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.True(t, accessMode < AccessModeWrite) | 	assert.True(t, accessMode < AccessModeWrite) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -84,9 +84,10 @@ func TestUser_GetTeams(t *testing.T) { | |||||||
| 	assert.NoError(t, PrepareTestDatabase()) | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
| 	org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) | 	org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User) | ||||||
| 	assert.NoError(t, org.GetTeams()) | 	assert.NoError(t, org.GetTeams()) | ||||||
| 	if assert.Len(t, org.Teams, 2) { | 	if assert.Len(t, org.Teams, 3) { | ||||||
| 		assert.Equal(t, int64(1), org.Teams[0].ID) | 		assert.Equal(t, int64(1), org.Teams[0].ID) | ||||||
| 		assert.Equal(t, int64(2), org.Teams[1].ID) | 		assert.Equal(t, int64(2), org.Teams[1].ID) | ||||||
|  | 		assert.Equal(t, int64(7), org.Teams[2].ID) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -458,7 +458,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := AccessLevel(doer.ID, pr.Issue.Repo) | 	mode, _ := AccessLevel(doer, pr.Issue.Repo) | ||||||
| 	if err = PrepareWebhooks(pr.Issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | 	if err = PrepareWebhooks(pr.Issue.Repo, HookEventPullRequest, &api.PullRequestPayload{ | ||||||
| 		Action:      api.HookIssueClosed, | 		Action:      api.HookIssueClosed, | ||||||
| 		Index:       pr.Index, | 		Index:       pr.Index, | ||||||
| @@ -787,7 +787,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str | |||||||
|  |  | ||||||
| 	pr.Issue = pull | 	pr.Issue = pull | ||||||
| 	pull.PullRequest = pr | 	pull.PullRequest = pr | ||||||
| 	mode, _ := AccessLevel(pull.Poster.ID, repo) | 	mode, _ := AccessLevel(pull.Poster, repo) | ||||||
| 	if err = PrepareWebhooks(repo, HookEventPullRequest, &api.PullRequestPayload{ | 	if err = PrepareWebhooks(repo, HookEventPullRequest, &api.PullRequestPayload{ | ||||||
| 		Action:      api.HookIssueOpened, | 		Action:      api.HookIssueOpened, | ||||||
| 		Index:       pull.Index, | 		Index:       pull.Index, | ||||||
|   | |||||||
| @@ -200,7 +200,7 @@ func CreateRelease(gitRepo *git.Repository, rel *Release, attachmentUUIDs []stri | |||||||
| 		if err := rel.LoadAttributes(); err != nil { | 		if err := rel.LoadAttributes(); err != nil { | ||||||
| 			log.Error(2, "LoadAttributes: %v", err) | 			log.Error(2, "LoadAttributes: %v", err) | ||||||
| 		} else { | 		} else { | ||||||
| 			mode, _ := AccessLevel(rel.PublisherID, rel.Repo) | 			mode, _ := AccessLevel(rel.Publisher, rel.Repo) | ||||||
| 			if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | 			if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | ||||||
| 				Action:     api.HookReleasePublished, | 				Action:     api.HookReleasePublished, | ||||||
| 				Release:    rel.APIFormat(), | 				Release:    rel.APIFormat(), | ||||||
| @@ -392,7 +392,7 @@ func UpdateRelease(doer *User, gitRepo *git.Repository, rel *Release, attachment | |||||||
|  |  | ||||||
| 	err = addReleaseAttachments(rel.ID, attachmentUUIDs) | 	err = addReleaseAttachments(rel.ID, attachmentUUIDs) | ||||||
|  |  | ||||||
| 	mode, _ := accessLevel(x, doer.ID, rel.Repo) | 	mode, _ := AccessLevel(doer, rel.Repo) | ||||||
| 	if err1 := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | 	if err1 := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | ||||||
| 		Action:     api.HookReleaseUpdated, | 		Action:     api.HookReleaseUpdated, | ||||||
| 		Release:    rel.APIFormat(), | 		Release:    rel.APIFormat(), | ||||||
| @@ -419,13 +419,6 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error { | |||||||
| 		return fmt.Errorf("GetRepositoryByID: %v", err) | 		return fmt.Errorf("GetRepositoryByID: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	has, err := HasAccess(u.ID, repo, AccessModeWrite) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("HasAccess: %v", err) |  | ||||||
| 	} else if !has { |  | ||||||
| 		return fmt.Errorf("DeleteReleaseByID: permission denied") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if delTag { | 	if delTag { | ||||||
| 		_, stderr, err := process.GetManager().ExecDir(-1, repo.RepoPath(), | 		_, stderr, err := process.GetManager().ExecDir(-1, repo.RepoPath(), | ||||||
| 			fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID), | 			fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID), | ||||||
| @@ -454,7 +447,7 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error { | |||||||
| 		return fmt.Errorf("LoadAttributes: %v", err) | 		return fmt.Errorf("LoadAttributes: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	mode, _ := accessLevel(x, u.ID, rel.Repo) | 	mode, _ := AccessLevel(u, rel.Repo) | ||||||
| 	if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | 	if err := PrepareWebhooks(rel.Repo, HookEventRelease, &api.ReleasePayload{ | ||||||
| 		Action:     api.HookReleaseDeleted, | 		Action:     api.HookReleaseDeleted, | ||||||
| 		Release:    rel.APIFormat(), | 		Release:    rel.APIFormat(), | ||||||
|   | |||||||
| @@ -325,63 +325,19 @@ func (repo *Repository) CheckUnitUser(userID int64, isAdmin bool, unitType UnitT | |||||||
| } | } | ||||||
|  |  | ||||||
| func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { | func (repo *Repository) checkUnitUser(e Engine, userID int64, isAdmin bool, unitType UnitType) bool { | ||||||
| 	if err := repo.getUnitsByUserID(e, userID, isAdmin); err != nil { | 	if isAdmin { | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, unit := range repo.Units { |  | ||||||
| 		if unit.Type == unitType { |  | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
|  | 	user, err := getUserByID(e, userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
| 	} | 	} | ||||||
|  | 	perm, err := getUserRepoPermission(e, repo, user) | ||||||
|  | 	if err != nil { | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| // LoadUnitsByUserID loads units according userID's permissions | 	return perm.CanRead(unitType) | ||||||
| func (repo *Repository) LoadUnitsByUserID(userID int64, isAdmin bool) error { |  | ||||||
| 	return repo.getUnitsByUserID(x, userID, isAdmin) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (err error) { |  | ||||||
| 	if repo.Units != nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err = repo.getUnits(e); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} else if err = repo.getOwner(e); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if !repo.Owner.IsOrganization() || userID == 0 || isAdmin || !repo.IsPrivate { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Collaborators will not be limited |  | ||||||
| 	if isCollaborator, err := repo.isCollaborator(e, userID); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} else if isCollaborator { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	teams, err := getUserRepoTeams(e, repo.OwnerID, userID, repo.ID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// unique |  | ||||||
| 	var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units)) |  | ||||||
| 	for _, u := range repo.Units { |  | ||||||
| 		for _, team := range teams { |  | ||||||
| 			if team.unitEnabled(e, u.Type) { |  | ||||||
| 				newRepoUnits = append(newRepoUnits, u) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	repo.Units = newRepoUnits |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // UnitEnabled if this repository has the given unit enabled | // UnitEnabled if this repository has the given unit enabled | ||||||
| @@ -397,21 +353,6 @@ func (repo *Repository) UnitEnabled(tp UnitType) bool { | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| // AnyUnitEnabled if this repository has the any of the given units enabled |  | ||||||
| func (repo *Repository) AnyUnitEnabled(tps ...UnitType) bool { |  | ||||||
| 	if err := repo.getUnits(x); err != nil { |  | ||||||
| 		log.Warn("Error loading repository (ID: %d) units: %s", repo.ID, err.Error()) |  | ||||||
| 	} |  | ||||||
| 	for _, unit := range repo.Units { |  | ||||||
| 		for _, tp := range tps { |  | ||||||
| 			if unit.Type == tp { |  | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// ErrUnitNotExist organization does not exist | 	// ErrUnitNotExist organization does not exist | ||||||
| 	ErrUnitNotExist = errors.New("Unit does not exist") | 	ErrUnitNotExist = errors.New("Unit does not exist") | ||||||
| @@ -600,11 +541,6 @@ func (repo *Repository) GetAssignees() (_ []*User, err error) { | |||||||
| 	return repo.getAssignees(x) | 	return repo.getAssignees(x) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetUserIfHasWriteAccess returns the user that has write access of repository by given ID. |  | ||||||
| func (repo *Repository) GetUserIfHasWriteAccess(userID int64) (*User, error) { |  | ||||||
| 	return GetUserIfHasWriteAccess(repo, userID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetMilestoneByID returns the milestone belongs to repository by given ID. | // GetMilestoneByID returns the milestone belongs to repository by given ID. | ||||||
| func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { | func (repo *Repository) GetMilestoneByID(milestoneID int64) (*Milestone, error) { | ||||||
| 	return GetMilestoneByRepoID(repo.ID, milestoneID) | 	return GetMilestoneByRepoID(repo.ID, milestoneID) | ||||||
| @@ -671,12 +607,6 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin | |||||||
| 	return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) | 	return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID) | ||||||
| } | } | ||||||
|  |  | ||||||
| // HasAccess returns true when user has access to this repository |  | ||||||
| func (repo *Repository) HasAccess(u *User) bool { |  | ||||||
| 	has, _ := HasAccess(u.ID, repo, AccessModeRead) |  | ||||||
| 	return has |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UpdateDefaultBranch updates the default branch | // UpdateDefaultBranch updates the default branch | ||||||
| func (repo *Repository) UpdateDefaultBranch() error { | func (repo *Repository) UpdateDefaultBranch() error { | ||||||
| 	_, err := x.ID(repo.ID).Cols("default_branch").Update(repo) | 	_, err := x.ID(repo.ID).Cols("default_branch").Update(repo) | ||||||
| @@ -704,11 +634,6 @@ func (repo *Repository) UpdateSize() error { | |||||||
| 	return repo.updateSize(x) | 	return repo.updateSize(x) | ||||||
| } | } | ||||||
|  |  | ||||||
| // CanBeForked returns true if repository meets the requirements of being forked. |  | ||||||
| func (repo *Repository) CanBeForked() bool { |  | ||||||
| 	return !repo.IsBare && repo.UnitEnabled(UnitTypeCode) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CanUserFork returns true if specified user can fork repository. | // CanUserFork returns true if specified user can fork repository. | ||||||
| func (repo *Repository) CanUserFork(user *User) (bool, error) { | func (repo *Repository) CanUserFork(user *User) (bool, error) { | ||||||
| 	if user == nil { | 	if user == nil { | ||||||
| @@ -2486,8 +2411,8 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	oldMode, _ := AccessLevel(doer.ID, oldRepo) | 	oldMode, _ := AccessLevel(doer, oldRepo) | ||||||
| 	mode, _ := AccessLevel(doer.ID, repo) | 	mode, _ := AccessLevel(doer, repo) | ||||||
|  |  | ||||||
| 	if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ | 	if err = PrepareWebhooks(oldRepo, HookEventFork, &api.ForkPayload{ | ||||||
| 		Forkee: oldRepo.APIFormat(oldMode), | 		Forkee: oldRepo.APIFormat(oldMode), | ||||||
|   | |||||||
							
								
								
									
										270
									
								
								models/repo_permission.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								models/repo_permission.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,270 @@ | |||||||
|  | // Copyright 2018 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 models | ||||||
|  |  | ||||||
|  | // Permission contains all the permissions related variables to a repository for a user | ||||||
|  | type Permission struct { | ||||||
|  | 	AccessMode AccessMode | ||||||
|  | 	Units      []*RepoUnit | ||||||
|  | 	UnitsMode  map[UnitType]AccessMode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsOwner returns true if current user is the owner of repository. | ||||||
|  | func (p *Permission) IsOwner() bool { | ||||||
|  | 	return p.AccessMode >= AccessModeOwner | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsAdmin returns true if current user has admin or higher access of repository. | ||||||
|  | func (p *Permission) IsAdmin() bool { | ||||||
|  | 	return p.AccessMode >= AccessModeAdmin | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HasAccess returns true if the current user has at least read access to any unit of this repository | ||||||
|  | func (p *Permission) HasAccess() bool { | ||||||
|  | 	if p.UnitsMode == nil { | ||||||
|  | 		return p.AccessMode >= AccessModeRead | ||||||
|  | 	} | ||||||
|  | 	return len(p.UnitsMode) > 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnitAccessMode returns current user accessmode to the specify unit of the repository | ||||||
|  | func (p *Permission) UnitAccessMode(unitType UnitType) AccessMode { | ||||||
|  | 	if p.UnitsMode == nil { | ||||||
|  | 		for _, u := range p.Units { | ||||||
|  | 			if u.Type == unitType { | ||||||
|  | 				return p.AccessMode | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return AccessModeNone | ||||||
|  | 	} | ||||||
|  | 	return p.UnitsMode[unitType] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanAccess returns true if user has mode access to the unit of the repository | ||||||
|  | func (p *Permission) CanAccess(mode AccessMode, unitType UnitType) bool { | ||||||
|  | 	return p.UnitAccessMode(unitType) >= mode | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanAccessAny returns true if user has mode access to any of the units of the repository | ||||||
|  | func (p *Permission) CanAccessAny(mode AccessMode, unitTypes ...UnitType) bool { | ||||||
|  | 	for _, u := range unitTypes { | ||||||
|  | 		if p.CanAccess(mode, u) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanRead returns true if user could read to this unit | ||||||
|  | func (p *Permission) CanRead(unitType UnitType) bool { | ||||||
|  | 	return p.CanAccess(AccessModeRead, unitType) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanReadAny returns true if user has read access to any of the units of the repository | ||||||
|  | func (p *Permission) CanReadAny(unitTypes ...UnitType) bool { | ||||||
|  | 	return p.CanAccessAny(AccessModeRead, unitTypes...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and | ||||||
|  | // returns true if isPull is false and user could read to issues | ||||||
|  | func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool { | ||||||
|  | 	if isPull { | ||||||
|  | 		return p.CanRead(UnitTypePullRequests) | ||||||
|  | 	} | ||||||
|  | 	return p.CanRead(UnitTypeIssues) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanWrite returns true if user could write to this unit | ||||||
|  | func (p *Permission) CanWrite(unitType UnitType) bool { | ||||||
|  | 	return p.CanAccess(AccessModeWrite, unitType) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and | ||||||
|  | // returns true if isPull is false and user could write to issues | ||||||
|  | func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { | ||||||
|  | 	if isPull { | ||||||
|  | 		return p.CanWrite(UnitTypePullRequests) | ||||||
|  | 	} | ||||||
|  | 	return p.CanWrite(UnitTypeIssues) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetUserRepoPermission returns the user permissions to the repository | ||||||
|  | func GetUserRepoPermission(repo *Repository, user *User) (Permission, error) { | ||||||
|  | 	return getUserRepoPermission(x, repo, user) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permission, err error) { | ||||||
|  | 	// anonymous user visit private repo. | ||||||
|  | 	// TODO: anonymous user visit public unit of private repo??? | ||||||
|  | 	if user == nil && repo.IsPrivate { | ||||||
|  | 		perm.AccessMode = AccessModeNone | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = repo.getUnits(e); err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	perm.Units = repo.Units | ||||||
|  |  | ||||||
|  | 	// anonymous visit public repo | ||||||
|  | 	if user == nil { | ||||||
|  | 		perm.AccessMode = AccessModeRead | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Admin or the owner has super access to the repository | ||||||
|  | 	if user.IsAdmin || user.ID == repo.OwnerID { | ||||||
|  | 		perm.AccessMode = AccessModeOwner | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// plain user | ||||||
|  | 	perm.AccessMode, err = accessLevel(e, user.ID, repo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = repo.getOwner(e); err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !repo.Owner.IsOrganization() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	perm.UnitsMode = make(map[UnitType]AccessMode) | ||||||
|  |  | ||||||
|  | 	// Collaborators on organization | ||||||
|  | 	if isCollaborator, err := repo.isCollaborator(e, user.ID); err != nil { | ||||||
|  | 		return perm, err | ||||||
|  | 	} else if isCollaborator { | ||||||
|  | 		for _, u := range repo.Units { | ||||||
|  | 			perm.UnitsMode[u.Type] = perm.AccessMode | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// get units mode from teams | ||||||
|  | 	teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, u := range repo.Units { | ||||||
|  | 		var found bool | ||||||
|  | 		for _, team := range teams { | ||||||
|  | 			if team.unitEnabled(e, u.Type) { | ||||||
|  | 				m := perm.UnitsMode[u.Type] | ||||||
|  | 				if m < team.Authorize { | ||||||
|  | 					perm.UnitsMode[u.Type] = team.Authorize | ||||||
|  | 				} | ||||||
|  | 				found = true | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// for a public repo on an organization, user have read permission on non-team defined units. | ||||||
|  | 		if !found && !repo.IsPrivate { | ||||||
|  | 			if _, ok := perm.UnitsMode[u.Type]; !ok { | ||||||
|  | 				perm.UnitsMode[u.Type] = AccessModeRead | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// remove no permission units | ||||||
|  | 	perm.Units = make([]*RepoUnit, 0, len(repo.Units)) | ||||||
|  | 	for t := range perm.UnitsMode { | ||||||
|  | 		for _, u := range repo.Units { | ||||||
|  | 			if u.Type == t { | ||||||
|  | 				perm.Units = append(perm.Units, u) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsUserRepoAdmin return ture if user has admin right of a repo | ||||||
|  | func IsUserRepoAdmin(repo *Repository, user *User) (bool, error) { | ||||||
|  | 	return isUserRepoAdmin(x, repo, user) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isUserRepoAdmin(e Engine, repo *Repository, user *User) (bool, error) { | ||||||
|  | 	if user == nil || repo == nil { | ||||||
|  | 		return false, nil | ||||||
|  | 	} | ||||||
|  | 	if user.IsAdmin { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mode, err := accessLevel(e, user.ID, repo) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	if mode >= AccessModeAdmin { | ||||||
|  | 		return true, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, team := range teams { | ||||||
|  | 		if team.Authorize >= AccessModeAdmin { | ||||||
|  | 			return true, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the | ||||||
|  | // user does not have access. | ||||||
|  | func AccessLevel(user *User, repo *Repository) (AccessMode, error) { | ||||||
|  | 	return accessLevelUnit(x, user, repo, UnitTypeCode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType) (AccessMode, error) { | ||||||
|  | 	perm, err := getUserRepoPermission(e, repo, user) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return AccessModeNone, err | ||||||
|  | 	} | ||||||
|  | 	return perm.UnitAccessMode(UnitTypeCode), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { | ||||||
|  | 	mode, err := accessLevelUnit(e, user, repo, unitType) | ||||||
|  | 	return testMode <= mode, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HasAccessUnit returns ture if user has testMode to the unit of the repository | ||||||
|  | func HasAccessUnit(user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { | ||||||
|  | 	return hasAccessUnit(x, user, repo, unitType, testMode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // canBeAssigned return true if user could be assigned to a repo | ||||||
|  | // FIXME: user could send PullRequest also could be assigned??? | ||||||
|  | func canBeAssigned(e Engine, user *User, repo *Repository) (bool, error) { | ||||||
|  | 	return hasAccessUnit(e, user, repo, UnitTypeCode, AccessModeWrite) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) { | ||||||
|  | 	var user *User | ||||||
|  | 	var err error | ||||||
|  | 	if userID > 0 { | ||||||
|  | 		user, err = getUserByID(e, userID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return false, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	perm, err := getUserRepoPermission(e, repo, user) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 	return perm.HasAccess(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // HasAccess returns true if user has access to repo | ||||||
|  | func HasAccess(userID int64, repo *Repository) (bool, error) { | ||||||
|  | 	return hasAccess(x, userID, repo) | ||||||
|  | } | ||||||
							
								
								
									
										246
									
								
								models/repo_permission_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								models/repo_permission_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | |||||||
|  | // Copyright 2018 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 models | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { | ||||||
|  | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	// public non-organization repo | ||||||
|  | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) | ||||||
|  | 	assert.NoError(t, repo.getUnits(x)) | ||||||
|  |  | ||||||
|  | 	// plain user | ||||||
|  | 	user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
|  | 	perm, err := GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// change to collaborator | ||||||
|  | 	assert.NoError(t, repo.AddCollaborator(user)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// collaborator | ||||||
|  | 	collaborator := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, collaborator) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// owner | ||||||
|  | 	owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, owner) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// admin | ||||||
|  | 	admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, admin) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { | ||||||
|  | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	// private non-organization repo | ||||||
|  | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) | ||||||
|  | 	assert.NoError(t, repo.getUnits(x)) | ||||||
|  |  | ||||||
|  | 	// plain user | ||||||
|  | 	user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) | ||||||
|  | 	perm, err := GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.False(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// change to collaborator to default write access | ||||||
|  | 	assert.NoError(t, repo.AddCollaborator(user)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// owner | ||||||
|  | 	owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, owner) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// admin | ||||||
|  | 	admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, admin) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRepoPermissionPublicOrgRepo(t *testing.T) { | ||||||
|  | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	// public organization repo | ||||||
|  | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 32}).(*Repository) | ||||||
|  | 	assert.NoError(t, repo.getUnits(x)) | ||||||
|  |  | ||||||
|  | 	// plain user | ||||||
|  | 	user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||||
|  | 	perm, err := GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// change to collaborator to default write access | ||||||
|  | 	assert.NoError(t, repo.AddCollaborator(user)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// org member team owner | ||||||
|  | 	owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, owner) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// org member team tester | ||||||
|  | 	member := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, member) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 	} | ||||||
|  | 	assert.True(t, perm.CanWrite(UnitTypeIssues)) | ||||||
|  | 	assert.False(t, perm.CanWrite(UnitTypeCode)) | ||||||
|  |  | ||||||
|  | 	// admin | ||||||
|  | 	admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, admin) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestRepoPermissionPrivateOrgRepo(t *testing.T) { | ||||||
|  | 	assert.NoError(t, PrepareTestDatabase()) | ||||||
|  |  | ||||||
|  | 	// private organization repo | ||||||
|  | 	repo := AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) | ||||||
|  | 	assert.NoError(t, repo.getUnits(x)) | ||||||
|  |  | ||||||
|  | 	// plain user | ||||||
|  | 	user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) | ||||||
|  | 	perm, err := GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.False(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// change to collaborator to default write access | ||||||
|  | 	assert.NoError(t, repo.AddCollaborator(user)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, user) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.False(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// org member team owner | ||||||
|  | 	owner := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, owner) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// org member team tester | ||||||
|  | 	tester := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, tester) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.True(t, perm.CanWrite(UnitTypeIssues)) | ||||||
|  | 	assert.False(t, perm.CanWrite(UnitTypeCode)) | ||||||
|  | 	assert.False(t, perm.CanRead(UnitTypeCode)) | ||||||
|  |  | ||||||
|  | 	// org member team reviewer | ||||||
|  | 	reviewer := AssertExistsAndLoadBean(t, &User{ID: 20}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, reviewer) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.False(t, perm.CanRead(UnitTypeIssues)) | ||||||
|  | 	assert.False(t, perm.CanWrite(UnitTypeCode)) | ||||||
|  | 	assert.True(t, perm.CanRead(UnitTypeCode)) | ||||||
|  |  | ||||||
|  | 	// admin | ||||||
|  | 	admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) | ||||||
|  | 	perm, err = GetUserRepoPermission(repo, admin) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	for _, unit := range repo.Units { | ||||||
|  | 		assert.True(t, perm.CanRead(unit.Type)) | ||||||
|  | 		assert.True(t, perm.CanWrite(unit.Type)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -166,10 +166,7 @@ func (r *RepoUnit) IssuesConfig() *IssuesConfig { | |||||||
| func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { | func (r *RepoUnit) ExternalTrackerConfig() *ExternalTrackerConfig { | ||||||
| 	return r.Config.(*ExternalTrackerConfig) | 	return r.Config.(*ExternalTrackerConfig) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { | func getUnitsByRepoID(e Engine, repoID int64) (units []*RepoUnit, err error) { | ||||||
| 	return units, e.Where("repo_id = ?", repoID).Find(&units) | 	return units, e.Where("repo_id = ?", repoID).Find(&units) | ||||||
| } | } | ||||||
|  |  | ||||||
| func getUnitsByRepoIDAndIDs(e Engine, repoID int64, types []UnitType) (units []*RepoUnit, err error) { |  | ||||||
| 	return units, e.Where("repo_id = ?", repoID).In("`type`", types).Find(&units) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -807,10 +807,10 @@ func DeleteDeployKey(doer *User, id int64) error { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("GetRepositoryByID: %v", err) | 			return fmt.Errorf("GetRepositoryByID: %v", err) | ||||||
| 		} | 		} | ||||||
| 		yes, err := HasAccess(doer.ID, repo, AccessModeAdmin) | 		has, err := IsUserRepoAdmin(repo, doer) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("HasAccess: %v", err) | 			return fmt.Errorf("GetUserRepoPermission: %v", err) | ||||||
| 		} else if !yes { | 		} else if !has { | ||||||
| 			return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"} | 			return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -496,24 +496,6 @@ func (u *User) DeleteAvatar() error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsAdminOfRepo returns true if user has admin or higher access of repository. |  | ||||||
| func (u *User) IsAdminOfRepo(repo *Repository) bool { |  | ||||||
| 	has, err := HasAccess(u.ID, repo, AccessModeAdmin) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error(3, "HasAccess: %v", err) |  | ||||||
| 	} |  | ||||||
| 	return has |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsWriterOfRepo returns true if user has write access to given repository. |  | ||||||
| func (u *User) IsWriterOfRepo(repo *Repository) bool { |  | ||||||
| 	has, err := HasAccess(u.ID, repo, AccessModeWrite) |  | ||||||
| 	if err != nil { |  | ||||||
| 		log.Error(3, "HasAccess: %v", err) |  | ||||||
| 	} |  | ||||||
| 	return has |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsOrganization returns true if user is actually a organization. | // IsOrganization returns true if user is actually a organization. | ||||||
| func (u *User) IsOrganization() bool { | func (u *User) IsOrganization() bool { | ||||||
| 	return u.Type == UserTypeOrganization | 	return u.Type == UserTypeOrganization | ||||||
| @@ -1170,17 +1152,6 @@ func GetUserByID(id int64) (*User, error) { | |||||||
| 	return getUserByID(x, id) | 	return getUserByID(x, id) | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetUserIfHasWriteAccess returns the user with write access of repository by given ID. |  | ||||||
| func GetUserIfHasWriteAccess(repo *Repository, userID int64) (*User, error) { |  | ||||||
| 	has, err := HasAccess(userID, repo, AccessModeWrite) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} else if !has { |  | ||||||
| 		return nil, ErrUserNotExist{userID, "", 0} |  | ||||||
| 	} |  | ||||||
| 	return GetUserByID(userID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetUserByName returns user by given name. | // GetUserByName returns user by given name. | ||||||
| func GetUserByName(name string) (*User, error) { | func GetUserByName(name string) (*User, error) { | ||||||
| 	return getUserByName(x, name) | 	return getUserByName(x, name) | ||||||
|   | |||||||
| @@ -169,7 +169,7 @@ func TestGetOrgRepositoryIDs(t *testing.T) { | |||||||
| 	accessibleRepos, err := user2.GetOrgRepositoryIDs() | 	accessibleRepos, err := user2.GetOrgRepositoryIDs() | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization | 	// User 2's team has access to private repos 3, 5, repo 32 is a public repo of the organization | ||||||
| 	assert.Equal(t, []int64{3, 5, 32}, accessibleRepos) | 	assert.Equal(t, []int64{3, 5, 23, 24, 32}, accessibleRepos) | ||||||
|  |  | ||||||
| 	accessibleRepos, err = user4.GetOrgRepositoryIDs() | 	accessibleRepos, err = user4.GetOrgRepositoryIDs() | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								modules/context/permission.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								modules/context/permission.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | // Copyright 2018 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 context | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.gitea.io/gitea/models" | ||||||
|  | 	macaron "gopkg.in/macaron.v1" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // RequireRepoAdmin returns a macaron middleware for requiring repository admin permission | ||||||
|  | func RequireRepoAdmin() macaron.Handler { | ||||||
|  | 	return func(ctx *Context) { | ||||||
|  | 		if !ctx.IsSigned || !ctx.Repo.IsAdmin() { | ||||||
|  | 			ctx.NotFound(ctx.Req.RequestURI, nil) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RequireRepoWriter returns a macaron middleware for requiring repository write to the specify unitType | ||||||
|  | func RequireRepoWriter(unitType models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *Context) { | ||||||
|  | 		if !ctx.Repo.CanWrite(unitType) { | ||||||
|  | 			ctx.NotFound(ctx.Req.RequestURI, nil) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RequireRepoWriterOr returns a macaron middleware for requiring repository write to one of the unit permission | ||||||
|  | func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *Context) { | ||||||
|  | 		for _, unitType := range unitTypes { | ||||||
|  | 			if ctx.Repo.CanWrite(unitType) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ctx.NotFound(ctx.Req.RequestURI, nil) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RequireRepoReader returns a macaron middleware for requiring repository read to the specify unitType | ||||||
|  | func RequireRepoReader(unitType models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *Context) { | ||||||
|  | 		if !ctx.Repo.CanRead(unitType) { | ||||||
|  | 			ctx.NotFound(ctx.Req.RequestURI, nil) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RequireRepoReaderOr returns a macaron middleware for requiring repository write to one of the unit permission | ||||||
|  | func RequireRepoReaderOr(unitTypes ...models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *Context) { | ||||||
|  | 		for _, unitType := range unitTypes { | ||||||
|  | 			if ctx.Repo.CanRead(unitType) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ctx.NotFound(ctx.Req.RequestURI, nil) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -32,7 +32,7 @@ type PullRequest struct { | |||||||
|  |  | ||||||
| // Repository contains information to operate a repository | // Repository contains information to operate a repository | ||||||
| type Repository struct { | type Repository struct { | ||||||
| 	AccessMode   models.AccessMode | 	models.Permission | ||||||
| 	IsWatching   bool | 	IsWatching   bool | ||||||
| 	IsViewBranch bool | 	IsViewBranch bool | ||||||
| 	IsViewTag    bool | 	IsViewTag    bool | ||||||
| @@ -54,34 +54,14 @@ type Repository struct { | |||||||
| 	PullRequest *PullRequest | 	PullRequest *PullRequest | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsOwner returns true if current user is the owner of repository. |  | ||||||
| func (r *Repository) IsOwner() bool { |  | ||||||
| 	return r.AccessMode >= models.AccessModeOwner |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsAdmin returns true if current user has admin or higher access of repository. |  | ||||||
| func (r *Repository) IsAdmin() bool { |  | ||||||
| 	return r.AccessMode >= models.AccessModeAdmin |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsWriter returns true if current user has write or higher access of repository. |  | ||||||
| func (r *Repository) IsWriter() bool { |  | ||||||
| 	return r.AccessMode >= models.AccessModeWrite |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // HasAccess returns true if the current user has at least read access for this repository |  | ||||||
| func (r *Repository) HasAccess() bool { |  | ||||||
| 	return r.AccessMode >= models.AccessModeRead |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CanEnableEditor returns true if repository is editable and user has proper access level. | // CanEnableEditor returns true if repository is editable and user has proper access level. | ||||||
| func (r *Repository) CanEnableEditor() bool { | func (r *Repository) CanEnableEditor() bool { | ||||||
| 	return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter() | 	return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch | ||||||
| } | } | ||||||
|  |  | ||||||
| // CanCreateBranch returns true if repository is editable and user has proper access level. | // CanCreateBranch returns true if repository is editable and user has proper access level. | ||||||
| func (r *Repository) CanCreateBranch() bool { | func (r *Repository) CanCreateBranch() bool { | ||||||
| 	return r.Repository.CanCreateBranch() && r.IsWriter() | 	return r.Permission.CanWrite(models.UnitTypeCode) && r.Repository.CanCreateBranch() | ||||||
| } | } | ||||||
|  |  | ||||||
| // CanCommitToBranch returns true if repository is editable and user has proper access level | // CanCommitToBranch returns true if repository is editable and user has proper access level | ||||||
| @@ -101,12 +81,12 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b | |||||||
| 	// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? | 	// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this? | ||||||
| 	isAssigned, _ := models.IsUserAssignedToIssue(issue, user) | 	isAssigned, _ := models.IsUserAssignedToIssue(issue, user) | ||||||
| 	return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() || | 	return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() || | ||||||
| 		r.IsWriter() || issue.IsPoster(user.ID) || isAssigned) | 		r.Permission.CanWrite(models.UnitTypeIssues) || issue.IsPoster(user.ID) || isAssigned) | ||||||
| } | } | ||||||
|  |  | ||||||
| // CanCreateIssueDependencies returns whether or not a user can create dependencies. | // CanCreateIssueDependencies returns whether or not a user can create dependencies. | ||||||
| func (r *Repository) CanCreateIssueDependencies(user *models.User) bool { | func (r *Repository) CanCreateIssueDependencies(user *models.User) bool { | ||||||
| 	return r.Repository.IsDependenciesEnabled() && r.IsWriter() | 	return r.Permission.CanWrite(models.UnitTypeIssues) && r.Repository.IsDependenciesEnabled() | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetCommitsCount returns cached commit count for current view | // GetCommitsCount returns cached commit count for current view | ||||||
| @@ -221,24 +201,15 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func repoAssignment(ctx *Context, repo *models.Repository) { | func repoAssignment(ctx *Context, repo *models.Repository) { | ||||||
| 	// Admin has super access. | 	var err error | ||||||
| 	if ctx.IsSigned && ctx.User.IsAdmin { | 	ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User) | ||||||
| 		ctx.Repo.AccessMode = models.AccessModeOwner |  | ||||||
| 	} else { |  | ||||||
| 		var userID int64 |  | ||||||
| 		if ctx.User != nil { |  | ||||||
| 			userID = ctx.User.ID |  | ||||||
| 		} |  | ||||||
| 		mode, err := models.AccessLevel(userID, repo) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 			ctx.ServerError("AccessLevel", err) | 		ctx.ServerError("GetUserRepoPermission", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 		ctx.Repo.AccessMode = mode |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Check access. | 	// Check access. | ||||||
| 	if ctx.Repo.AccessMode == models.AccessModeNone { | 	if ctx.Repo.Permission.AccessMode == models.AccessModeNone { | ||||||
| 		if ctx.Query("go-get") == "1" { | 		if ctx.Query("go-get") == "1" { | ||||||
| 			EarlyResponseForGoGetMeta(ctx) | 			EarlyResponseForGoGetMeta(ctx) | ||||||
| 			return | 			return | ||||||
| @@ -247,6 +218,7 @@ func repoAssignment(ctx *Context, repo *models.Repository) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["HasAccess"] = true | 	ctx.Data["HasAccess"] = true | ||||||
|  | 	ctx.Data["Permission"] = &ctx.Repo.Permission | ||||||
|  |  | ||||||
| 	if repo.IsMirror { | 	if repo.IsMirror { | ||||||
| 		var err error | 		var err error | ||||||
| @@ -281,10 +253,6 @@ func RepoIDAssignment() macaron.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err = repo.GetOwner(); err != nil { |  | ||||||
| 			ctx.ServerError("GetOwner", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		repoAssignment(ctx, repo) | 		repoAssignment(ctx, repo) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -381,7 +349,9 @@ func RepoAssignment() macaron.Handler { | |||||||
| 		ctx.Data["Owner"] = ctx.Repo.Repository.Owner | 		ctx.Data["Owner"] = ctx.Repo.Repository.Owner | ||||||
| 		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() | 		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner() | ||||||
| 		ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() | 		ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin() | ||||||
| 		ctx.Data["IsRepositoryWriter"] = ctx.Repo.IsWriter() | 		ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) | ||||||
|  | 		ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) | ||||||
|  | 		ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) | ||||||
|  |  | ||||||
| 		if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { | 		if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { | ||||||
| 			ctx.ServerError("CanUserFork", err) | 			ctx.ServerError("CanUserFork", err) | ||||||
| @@ -435,7 +405,7 @@ func RepoAssignment() macaron.Handler { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// People who have push access or have forked repository can propose a new pull request. | 		// People who have push access or have forked repository can propose a new pull request. | ||||||
| 		if ctx.Repo.IsWriter() || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) { | 		if ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) { | ||||||
| 			// Pull request is allowed if this is a fork repository | 			// Pull request is allowed if this is a fork repository | ||||||
| 			// and base repository accepts pull requests. | 			// and base repository accepts pull requests. | ||||||
| 			if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { | 			if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() { | ||||||
| @@ -453,9 +423,6 @@ func RepoAssignment() macaron.Handler { | |||||||
| 					ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName | 					ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Reset repo units as otherwise user specific units wont be loaded later |  | ||||||
| 			ctx.Repo.Repository.Units = nil |  | ||||||
| 		} | 		} | ||||||
| 		ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest | 		ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest | ||||||
|  |  | ||||||
| @@ -661,64 +628,6 @@ func RepoRefByType(refType RepoRefType) macaron.Handler { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // RequireRepoAdmin returns a macaron middleware for requiring repository admin permission |  | ||||||
| func RequireRepoAdmin() macaron.Handler { |  | ||||||
| 	return func(ctx *Context) { |  | ||||||
| 		if !ctx.IsSigned || (!ctx.Repo.IsAdmin() && !ctx.User.IsAdmin) { |  | ||||||
| 			ctx.NotFound(ctx.Req.RequestURI, nil) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // RequireRepoWriter returns a macaron middleware for requiring repository write permission |  | ||||||
| func RequireRepoWriter() macaron.Handler { |  | ||||||
| 	return func(ctx *Context) { |  | ||||||
| 		if !ctx.IsSigned || (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) { |  | ||||||
| 			ctx.NotFound(ctx.Req.RequestURI, nil) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // LoadRepoUnits loads repsitory's units, it should be called after repository and user loaded |  | ||||||
| func LoadRepoUnits() macaron.Handler { |  | ||||||
| 	return func(ctx *Context) { |  | ||||||
| 		var isAdmin bool |  | ||||||
| 		if ctx.User != nil && ctx.User.IsAdmin { |  | ||||||
| 			isAdmin = true |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var userID int64 |  | ||||||
| 		if ctx.User != nil { |  | ||||||
| 			userID = ctx.User.ID |  | ||||||
| 		} |  | ||||||
| 		err := ctx.Repo.Repository.LoadUnitsByUserID(userID, isAdmin) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("LoadUnitsByUserID", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CheckUnit will check whether unit type is enabled |  | ||||||
| func CheckUnit(unitType models.UnitType) macaron.Handler { |  | ||||||
| 	return func(ctx *Context) { |  | ||||||
| 		if !ctx.Repo.Repository.UnitEnabled(unitType) { |  | ||||||
| 			ctx.NotFound("CheckUnit", fmt.Errorf("%s: %v", ctx.Tr("units.error.unit_not_allowed"), unitType)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // CheckAnyUnit will check whether any of the unit types are enabled |  | ||||||
| func CheckAnyUnit(unitTypes ...models.UnitType) macaron.Handler { |  | ||||||
| 	return func(ctx *Context) { |  | ||||||
| 		if !ctx.Repo.Repository.AnyUnitEnabled(unitTypes...) { |  | ||||||
| 			ctx.NotFound("CheckAnyUnit", fmt.Errorf("%s: %v", ctx.Tr("units.error.unit_not_allowed"), unitTypes)) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GitHookService checks if repository Git hooks service has been enabled. | // GitHookService checks if repository Git hooks service has been enabled. | ||||||
| func GitHookService() macaron.Handler { | func GitHookService() macaron.Handler { | ||||||
| 	return func(ctx *Context) { | 	return func(ctx *Context) { | ||||||
|   | |||||||
| @@ -497,12 +497,12 @@ func authenticate(ctx *context.Context, repository *models.Repository, authoriza | |||||||
| 		accessMode = models.AccessModeWrite | 		accessMode = models.AccessModeWrite | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !repository.IsPrivate && !requireWrite { | 	perm, err := models.GetUserRepoPermission(repository, ctx.User) | ||||||
| 		return true | 	if err != nil { | ||||||
|  | 		return false | ||||||
| 	} | 	} | ||||||
| 	if ctx.IsSigned { | 	if ctx.IsSigned { | ||||||
| 		accessCheck, _ := models.HasAccess(ctx.User.ID, repository, accessMode) | 		return perm.CanAccess(accessMode, models.UnitTypeCode) | ||||||
| 		return accessCheck |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	user, repo, opStr, err := parseToken(authorization) | 	user, repo, opStr, err := parseToken(authorization) | ||||||
| @@ -511,8 +511,11 @@ func authenticate(ctx *context.Context, repository *models.Repository, authoriza | |||||||
| 	} | 	} | ||||||
| 	ctx.User = user | 	ctx.User = user | ||||||
| 	if opStr == "basic" { | 	if opStr == "basic" { | ||||||
| 		accessCheck, _ := models.HasAccess(ctx.User.ID, repository, accessMode) | 		perm, err = models.GetUserRepoPermission(repository, ctx.User) | ||||||
| 		return accessCheck | 		if err != nil { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 		return perm.CanAccess(accessMode, models.UnitTypeCode) | ||||||
| 	} | 	} | ||||||
| 	if repository.ID == repo.ID { | 	if repository.ID == repo.ID { | ||||||
| 		if requireWrite && opStr != "upload" { | 		if requireWrite && opStr != "upload" { | ||||||
|   | |||||||
| @@ -51,27 +51,9 @@ func newInternalRequest(url, method string) *httplib.Request { | |||||||
| } | } | ||||||
|  |  | ||||||
| // CheckUnitUser check whether user could visit the unit of this repository | // CheckUnitUser check whether user could visit the unit of this repository | ||||||
| func CheckUnitUser(userID, repoID int64, isAdmin bool, unitType models.UnitType) (bool, error) { | func CheckUnitUser(userID, repoID int64, isAdmin bool, unitType models.UnitType) (*models.AccessMode, error) { | ||||||
| 	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/checkunituser?isAdmin=%t&unitType=%d", repoID, userID, isAdmin, unitType) | 	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/checkunituser?isAdmin=%t&unitType=%d", repoID, userID, isAdmin, unitType) | ||||||
| 	log.GitLogger.Trace("AccessLevel: %s", reqURL) | 	log.GitLogger.Trace("CheckUnitUser: %s", reqURL) | ||||||
|  |  | ||||||
| 	resp, err := newInternalRequest(reqURL, "GET").Response() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false, err |  | ||||||
| 	} |  | ||||||
| 	defer resp.Body.Close() |  | ||||||
|  |  | ||||||
| 	if resp.StatusCode == 200 { |  | ||||||
| 		return true, nil |  | ||||||
| 	} |  | ||||||
| 	return false, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the |  | ||||||
| // user does not have access. |  | ||||||
| func AccessLevel(userID, repoID int64) (*models.AccessMode, error) { |  | ||||||
| 	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/accesslevel", repoID, userID) |  | ||||||
| 	log.GitLogger.Trace("AccessLevel: %s", reqURL) |  | ||||||
|  |  | ||||||
| 	resp, err := newInternalRequest(reqURL, "GET").Response() | 	resp, err := newInternalRequest(reqURL, "GET").Response() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -80,7 +62,7 @@ func AccessLevel(userID, repoID int64) (*models.AccessMode, error) { | |||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
| 	if resp.StatusCode != 200 { | 	if resp.StatusCode != 200 { | ||||||
| 		return nil, fmt.Errorf("Failed to get user access level: %s", decodeJSONError(resp).Err) | 		return nil, fmt.Errorf("Failed to CheckUnitUser: %s", decodeJSONError(resp).Err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var a models.AccessMode | 	var a models.AccessMode | ||||||
|   | |||||||
| @@ -47,6 +47,9 @@ func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) { | |||||||
| 	ctx.Repo = &context.Repository{} | 	ctx.Repo = &context.Repository{} | ||||||
| 	ctx.Repo.Repository = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) | 	ctx.Repo.Repository = models.AssertExistsAndLoadBean(t, &models.Repository{ID: repoID}).(*models.Repository) | ||||||
| 	ctx.Repo.RepoLink = ctx.Repo.Repository.Link() | 	ctx.Repo.RepoLink = ctx.Repo.Repository.Link() | ||||||
|  | 	var err error | ||||||
|  | 	ctx.Repo.Permission, err = models.GetUserRepoPermission(ctx.Repo.Repository, ctx.User) | ||||||
|  | 	assert.NoError(t, err) | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadRepoCommit loads a repo's commit into a test context. | // LoadRepoCommit loads a repo's commit into a test context. | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | // Copyright 2015 The Gogs Authors. All rights reserved. | ||||||
| // Copyright 2018 The Gitea Authors. All rights reserved. | // Copyright 2016 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -71,7 +71,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/routers/api/v1/repo" | 	"code.gitea.io/gitea/routers/api/v1/repo" | ||||||
| 	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation | 	_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/user" | 	"code.gitea.io/gitea/routers/api/v1/user" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" |  | ||||||
| 	api "code.gitea.io/sdk/gitea" | 	api "code.gitea.io/sdk/gitea" | ||||||
|  |  | ||||||
| 	"github.com/go-macaron/binding" | 	"github.com/go-macaron/binding" | ||||||
| @@ -152,24 +151,18 @@ func repoAssignment() macaron.Handler { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		repo.Owner = owner | 		repo.Owner = owner | ||||||
|  | 		ctx.Repo.Repository = repo | ||||||
|  |  | ||||||
| 		if ctx.IsSigned && ctx.User.IsAdmin { | 		ctx.Repo.Permission, err = models.GetUserRepoPermission(repo, ctx.User) | ||||||
| 			ctx.Repo.AccessMode = models.AccessModeOwner |  | ||||||
| 		} else { |  | ||||||
| 			mode, err := models.AccessLevel(utils.UserID(ctx), repo) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 				ctx.Error(500, "AccessLevel", err) | 			ctx.Error(500, "GetUserRepoPermission", err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 			ctx.Repo.AccessMode = mode |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !ctx.Repo.HasAccess() { | 		if !ctx.Repo.HasAccess() { | ||||||
| 			ctx.Status(404) | 			ctx.Status(404) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		ctx.Repo.Repository = repo |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -196,7 +189,8 @@ func reqBasicAuth() macaron.Handler { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func reqAdmin() macaron.Handler { | // reqSiteAdmin user should be the site admin | ||||||
|  | func reqSiteAdmin() macaron.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		if !ctx.IsSigned || !ctx.User.IsAdmin { | 		if !ctx.IsSigned || !ctx.User.IsAdmin { | ||||||
| 			ctx.Error(403) | 			ctx.Error(403) | ||||||
| @@ -205,15 +199,56 @@ func reqAdmin() macaron.Handler { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func reqRepoWriter() macaron.Handler { | // reqOwner user should be the owner of the repo. | ||||||
|  | func reqOwner() macaron.Handler { | ||||||
| 	return func(ctx *context.Context) { | 	return func(ctx *context.Context) { | ||||||
| 		if !ctx.Repo.IsWriter() { | 		if !ctx.Repo.IsOwner() { | ||||||
| 			ctx.Error(403) | 			ctx.Error(403) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // reqAdmin user should be an owner or a collaborator with admin write of a repository | ||||||
|  | func reqAdmin() macaron.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		if !ctx.Repo.IsAdmin() { | ||||||
|  | 			ctx.Error(403) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func reqRepoReader(unitType models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		if !ctx.Repo.CanRead(unitType) { | ||||||
|  | 			ctx.Error(403) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func reqAnyRepoReader() macaron.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		if !ctx.Repo.HasAccess() { | ||||||
|  | 			ctx.Error(403) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler { | ||||||
|  | 	return func(ctx *context.Context) { | ||||||
|  | 		for _, unitType := range unitTypes { | ||||||
|  | 			if ctx.Repo.CanWrite(unitType) { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ctx.Error(403) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func reqOrgMembership() macaron.Handler { | func reqOrgMembership() macaron.Handler { | ||||||
| 	return func(ctx *context.APIContext) { | 	return func(ctx *context.APIContext) { | ||||||
| 		var orgID int64 | 		var orgID int64 | ||||||
| @@ -308,22 +343,22 @@ func orgAssignment(args ...bool) macaron.Handler { | |||||||
| } | } | ||||||
|  |  | ||||||
| func mustEnableIssues(ctx *context.APIContext) { | func mustEnableIssues(ctx *context.APIContext) { | ||||||
| 	if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) { | 	if !ctx.Repo.CanRead(models.UnitTypeIssues) { | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func mustAllowPulls(ctx *context.Context) { | func mustAllowPulls(ctx *context.Context) { | ||||||
| 	if !ctx.Repo.Repository.AllowsPulls() { | 	if !(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(models.UnitTypePullRequests)) { | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func mustEnableIssuesOrPulls(ctx *context.Context) { | func mustEnableIssuesOrPulls(ctx *context.Context) { | ||||||
| 	if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) && | 	if !ctx.Repo.CanRead(models.UnitTypeIssues) && | ||||||
| 		!ctx.Repo.Repository.AllowsPulls() { | 		!(ctx.Repo.Repository.CanEnablePulls() && ctx.Repo.CanRead(models.UnitTypePullRequests)) { | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -443,7 +478,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate) | 			m.Post("/migrate", reqToken(), bind(auth.MigrateRepoForm{}), repo.Migrate) | ||||||
|  |  | ||||||
| 			m.Group("/:username/:reponame", func() { | 			m.Group("/:username/:reponame", func() { | ||||||
| 				m.Combo("").Get(repo.Get).Delete(reqToken(), repo.Delete) | 				m.Combo("").Get(reqAnyRepoReader(), repo.Get). | ||||||
|  | 					Delete(reqToken(), reqOwner(), repo.Delete) | ||||||
| 				m.Group("/hooks", func() { | 				m.Group("/hooks", func() { | ||||||
| 					m.Combo("").Get(repo.ListHooks). | 					m.Combo("").Get(repo.ListHooks). | ||||||
| 						Post(bind(api.CreateHookOption{}), repo.CreateHook) | 						Post(bind(api.CreateHookOption{}), repo.CreateHook) | ||||||
| @@ -453,31 +489,30 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 							Delete(repo.DeleteHook) | 							Delete(repo.DeleteHook) | ||||||
| 						m.Post("/tests", context.RepoRef(), repo.TestHook) | 						m.Post("/tests", context.RepoRef(), repo.TestHook) | ||||||
| 					}) | 					}) | ||||||
| 				}, reqToken(), reqRepoWriter()) | 				}, reqToken(), reqAdmin()) | ||||||
| 				m.Group("/collaborators", func() { | 				m.Group("/collaborators", func() { | ||||||
| 					m.Get("", repo.ListCollaborators) | 					m.Get("", repo.ListCollaborators) | ||||||
| 					m.Combo("/:collaborator").Get(repo.IsCollaborator). | 					m.Combo("/:collaborator").Get(repo.IsCollaborator). | ||||||
| 						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator). | 						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator). | ||||||
| 						Delete(repo.DeleteCollaborator) | 						Delete(repo.DeleteCollaborator) | ||||||
| 				}, reqToken()) | 				}, reqToken(), reqAdmin()) | ||||||
| 				m.Get("/raw/*", context.RepoRefByType(context.RepoRefAny), repo.GetRawFile) | 				m.Get("/raw/*", context.RepoRefByType(context.RepoRefAny), reqRepoReader(models.UnitTypeCode), repo.GetRawFile) | ||||||
| 				m.Get("/archive/*", repo.GetArchive) | 				m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive) | ||||||
| 				m.Combo("/forks").Get(repo.ListForks). | 				m.Combo("/forks").Get(repo.ListForks). | ||||||
| 					Post(reqToken(), bind(api.CreateForkOption{}), repo.CreateFork) | 					Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork) | ||||||
| 				m.Group("/branches", func() { | 				m.Group("/branches", func() { | ||||||
| 					m.Get("", repo.ListBranches) | 					m.Get("", repo.ListBranches) | ||||||
| 					m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) | 					m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch) | ||||||
| 				}) | 				}, reqRepoReader(models.UnitTypeCode)) | ||||||
| 				m.Group("/keys", func() { | 				m.Group("/keys", func() { | ||||||
| 					m.Combo("").Get(repo.ListDeployKeys). | 					m.Combo("").Get(repo.ListDeployKeys). | ||||||
| 						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) | 						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) | ||||||
| 					m.Combo("/:id").Get(repo.GetDeployKey). | 					m.Combo("/:id").Get(repo.GetDeployKey). | ||||||
| 						Delete(repo.DeleteDeploykey) | 						Delete(repo.DeleteDeploykey) | ||||||
| 				}, reqToken(), reqRepoWriter()) | 				}, reqToken(), reqAdmin()) | ||||||
| 				m.Group("/times", func() { | 				m.Group("/times", func() { | ||||||
| 					m.Combo("").Get(repo.ListTrackedTimesByRepository) | 					m.Combo("").Get(repo.ListTrackedTimesByRepository) | ||||||
| 					m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser) | 					m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser) | ||||||
|  |  | ||||||
| 				}, mustEnableIssues) | 				}, mustEnableIssues) | ||||||
| 				m.Group("/issues", func() { | 				m.Group("/issues", func() { | ||||||
| 					m.Combo("").Get(repo.ListIssues). | 					m.Combo("").Get(repo.ListIssues). | ||||||
| @@ -517,17 +552,17 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 				}, mustEnableIssuesOrPulls) | 				}, mustEnableIssuesOrPulls) | ||||||
| 				m.Group("/labels", func() { | 				m.Group("/labels", func() { | ||||||
| 					m.Combo("").Get(repo.ListLabels). | 					m.Combo("").Get(repo.ListLabels). | ||||||
| 						Post(reqToken(), bind(api.CreateLabelOption{}), repo.CreateLabel) | 						Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel) | ||||||
| 					m.Combo("/:id").Get(repo.GetLabel). | 					m.Combo("/:id").Get(repo.GetLabel). | ||||||
| 						Patch(reqToken(), bind(api.EditLabelOption{}), repo.EditLabel). | 						Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel). | ||||||
| 						Delete(reqToken(), repo.DeleteLabel) | 						Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel) | ||||||
| 				}) | 				}) | ||||||
| 				m.Group("/milestones", func() { | 				m.Group("/milestones", func() { | ||||||
| 					m.Combo("").Get(repo.ListMilestones). | 					m.Combo("").Get(repo.ListMilestones). | ||||||
| 						Post(reqToken(), reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) | 						Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) | ||||||
| 					m.Combo("/:id").Get(repo.GetMilestone). | 					m.Combo("/:id").Get(repo.GetMilestone). | ||||||
| 						Patch(reqToken(), reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone). | 						Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). | ||||||
| 						Delete(reqToken(), reqRepoWriter(), repo.DeleteMilestone) | 						Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone) | ||||||
| 				}) | 				}) | ||||||
| 				m.Get("/stargazers", repo.ListStargazers) | 				m.Get("/stargazers", repo.ListStargazers) | ||||||
| 				m.Get("/subscribers", repo.ListSubscribers) | 				m.Get("/subscribers", repo.ListSubscribers) | ||||||
| @@ -538,45 +573,44 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 				}) | 				}) | ||||||
| 				m.Group("/releases", func() { | 				m.Group("/releases", func() { | ||||||
| 					m.Combo("").Get(repo.ListReleases). | 					m.Combo("").Get(repo.ListReleases). | ||||||
| 						Post(reqToken(), reqRepoWriter(), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) | 						Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) | ||||||
| 					m.Group("/:id", func() { | 					m.Group("/:id", func() { | ||||||
| 						m.Combo("").Get(repo.GetRelease). | 						m.Combo("").Get(repo.GetRelease). | ||||||
| 							Patch(reqToken(), reqRepoWriter(), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease). | 							Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease). | ||||||
| 							Delete(reqToken(), reqRepoWriter(), repo.DeleteRelease) | 							Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease) | ||||||
| 						m.Group("/assets", func() { | 						m.Group("/assets", func() { | ||||||
| 							m.Combo("").Get(repo.ListReleaseAttachments). | 							m.Combo("").Get(repo.ListReleaseAttachments). | ||||||
| 								Post(reqToken(), reqRepoWriter(), repo.CreateReleaseAttachment) | 								Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.CreateReleaseAttachment) | ||||||
| 							m.Combo("/:asset").Get(repo.GetReleaseAttachment). | 							m.Combo("/:asset").Get(repo.GetReleaseAttachment). | ||||||
| 								Patch(reqToken(), reqRepoWriter(), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). | 								Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). | ||||||
| 								Delete(reqToken(), reqRepoWriter(), repo.DeleteReleaseAttachment) | 								Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment) | ||||||
| 						}) | 						}) | ||||||
| 					}) | 					}) | ||||||
| 				}) | 				}, reqRepoReader(models.UnitTypeReleases)) | ||||||
| 				m.Post("/mirror-sync", reqToken(), reqRepoWriter(), repo.MirrorSync) | 				m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync) | ||||||
| 				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig) | 				m.Get("/editorconfig/:filename", context.RepoRef(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig) | ||||||
| 				m.Group("/pulls", func() { | 				m.Group("/pulls", func() { | ||||||
| 					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests). | 					m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests). | ||||||
| 						Post(reqToken(), reqRepoWriter(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) | 						Post(reqToken(), bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) | ||||||
| 					m.Group("/:index", func() { | 					m.Group("/:index", func() { | ||||||
| 						m.Combo("").Get(repo.GetPullRequest). | 						m.Combo("").Get(repo.GetPullRequest). | ||||||
| 							Patch(reqToken(), reqRepoWriter(), bind(api.EditPullRequestOption{}), repo.EditPullRequest) | 							Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest) | ||||||
| 						m.Combo("/merge").Get(repo.IsPullRequestMerged). | 						m.Combo("/merge").Get(repo.IsPullRequestMerged). | ||||||
| 							Post(reqToken(), reqRepoWriter(), bind(auth.MergePullRequestForm{}), repo.MergePullRequest) | 							Post(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(auth.MergePullRequestForm{}), repo.MergePullRequest) | ||||||
| 					}) | 					}) | ||||||
|  | 				}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo()) | ||||||
| 				}, mustAllowPulls, context.ReferencesGitRepo()) |  | ||||||
| 				m.Group("/statuses", func() { | 				m.Group("/statuses", func() { | ||||||
| 					m.Combo("/:sha").Get(repo.GetCommitStatuses). | 					m.Combo("/:sha").Get(repo.GetCommitStatuses). | ||||||
| 						Post(reqToken(), reqRepoWriter(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) | 						Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) | ||||||
| 				}) | 				}, reqRepoReader(models.UnitTypeCode)) | ||||||
| 				m.Group("/commits/:ref", func() { | 				m.Group("/commits/:ref", func() { | ||||||
| 					m.Get("/status", repo.GetCombinedCommitStatusByRef) | 					m.Get("/status", repo.GetCombinedCommitStatusByRef) | ||||||
| 					m.Get("/statuses", repo.GetCommitStatusesByRef) | 					m.Get("/statuses", repo.GetCommitStatusesByRef) | ||||||
| 				}) | 				}, reqRepoReader(models.UnitTypeCode)) | ||||||
| 				m.Group("/git", func() { | 				m.Group("/git", func() { | ||||||
| 					m.Get("/refs", repo.GetGitAllRefs) | 					m.Get("/refs", repo.GetGitAllRefs) | ||||||
| 					m.Get("/refs/*", repo.GetGitRefs) | 					m.Get("/refs/*", repo.GetGitRefs) | ||||||
| 				}) | 				}, reqRepoReader(models.UnitTypeCode)) | ||||||
| 			}, repoAssignment()) | 			}, repoAssignment()) | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| @@ -645,7 +679,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 					m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) | 					m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) | ||||||
| 				}) | 				}) | ||||||
| 			}) | 			}) | ||||||
| 		}, reqToken(), reqAdmin()) | 		}, reqToken(), reqSiteAdmin()) | ||||||
|  |  | ||||||
| 		m.Group("/topics", func() { | 		m.Group("/topics", func() { | ||||||
| 			m.Get("/search", repo.TopicSearch) | 			m.Get("/search", repo.TopicSearch) | ||||||
|   | |||||||
| @@ -311,7 +311,7 @@ func GetTeamRepos(ctx *context.APIContext) { | |||||||
| 	} | 	} | ||||||
| 	repos := make([]*api.Repository, len(team.Repos)) | 	repos := make([]*api.Repository, len(team.Repos)) | ||||||
| 	for i, repo := range team.Repos { | 	for i, repo := range team.Repos { | ||||||
| 		access, err := models.AccessLevel(ctx.User.ID, repo) | 		access, err := models.AccessLevel(ctx.User, repo) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Error(500, "GetTeamRepos", err) | 			ctx.Error(500, "GetTeamRepos", err) | ||||||
| 			return | 			return | ||||||
| @@ -366,7 +366,7 @@ func AddTeamRepository(ctx *context.APIContext) { | |||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if access, err := models.AccessLevel(ctx.User.ID, repo); err != nil { | 	if access, err := models.AccessLevel(ctx.User, repo); err != nil { | ||||||
| 		ctx.Error(500, "AccessLevel", err) | 		ctx.Error(500, "AccessLevel", err) | ||||||
| 		return | 		return | ||||||
| 	} else if access < models.AccessModeAdmin { | 	} else if access < models.AccessModeAdmin { | ||||||
| @@ -413,7 +413,7 @@ func RemoveTeamRepository(ctx *context.APIContext) { | |||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if access, err := models.AccessLevel(ctx.User.ID, repo); err != nil { | 	if access, err := models.AccessLevel(ctx.User, repo); err != nil { | ||||||
| 		ctx.Error(500, "AccessLevel", err) | 		ctx.Error(500, "AccessLevel", err) | ||||||
| 		return | 		return | ||||||
| 	} else if access < models.AccessModeAdmin { | 	} else if access < models.AccessModeAdmin { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2016 The Gogs Authors. All rights reserved. | // Copyright 2016 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -34,10 +35,6 @@ func ListCollaborators(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/UserList" | 	//     "$ref": "#/responses/UserList" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Error(403, "", "User does not have push access") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	collaborators, err := ctx.Repo.Repository.GetCollaborators() | 	collaborators, err := ctx.Repo.Repository.GetCollaborators() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "ListCollaborators", err) | 		ctx.Error(500, "ListCollaborators", err) | ||||||
| @@ -78,10 +75,6 @@ func IsCollaborator(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	//   "404": | 	//   "404": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Error(403, "", "User does not have push access") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	user, err := models.GetUserByName(ctx.Params(":collaborator")) | 	user, err := models.GetUserByName(ctx.Params(":collaborator")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrUserNotExist(err) { | 		if models.IsErrUserNotExist(err) { | ||||||
| @@ -133,10 +126,6 @@ func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Error(403, "", "User does not have push access") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) | 	collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrUserNotExist(err) { | 		if models.IsErrUserNotExist(err) { | ||||||
| @@ -193,11 +182,6 @@ func DeleteCollaborator(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Error(403, "", "User does not have push access") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) | 	collaborator, err := models.GetUserByName(ctx.Params(":collaborator")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrUserNotExist(err) { | 		if models.IsErrUserNotExist(err) { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -38,11 +39,6 @@ func GetRawFile(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   200: | 	//   200: | ||||||
| 	//     description: success | 	//     description: success | ||||||
| 	if !ctx.Repo.HasAccess() { |  | ||||||
| 		ctx.Status(404) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if ctx.Repo.Repository.IsBare { | 	if ctx.Repo.Repository.IsBare { | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ package repo | |||||||
| import ( | import ( | ||||||
| 	"code.gitea.io/gitea/models" | 	"code.gitea.io/gitea/models" | ||||||
| 	"code.gitea.io/gitea/modules/context" | 	"code.gitea.io/gitea/modules/context" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" |  | ||||||
|  |  | ||||||
| 	api "code.gitea.io/sdk/gitea" | 	api "code.gitea.io/sdk/gitea" | ||||||
| ) | ) | ||||||
| @@ -40,7 +39,7 @@ func ListForks(ctx *context.APIContext) { | |||||||
| 	} | 	} | ||||||
| 	apiForks := make([]*api.Repository, len(forks)) | 	apiForks := make([]*api.Repository, len(forks)) | ||||||
| 	for i, fork := range forks { | 	for i, fork := range forks { | ||||||
| 		access, err := models.AccessLevel(utils.UserID(ctx), fork) | 		access, err := models.AccessLevel(ctx.User, fork) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Error(500, "AccessLevel", err) | 			ctx.Error(500, "AccessLevel", err) | ||||||
| 			return | 			return | ||||||
|   | |||||||
| @@ -169,7 +169,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||||||
| 	//     "$ref": "#/responses/Issue" | 	//     "$ref": "#/responses/Issue" | ||||||
|  |  | ||||||
| 	var deadlineUnix util.TimeStamp | 	var deadlineUnix util.TimeStamp | ||||||
| 	if form.Deadline != nil && ctx.Repo.IsWriter() { | 	if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | 		deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -184,7 +184,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) { | |||||||
|  |  | ||||||
| 	var assigneeIDs = make([]int64, 0) | 	var assigneeIDs = make([]int64, 0) | ||||||
| 	var err error | 	var err error | ||||||
| 	if ctx.Repo.IsWriter() { | 	if ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		issue.MilestoneID = form.Milestone | 		issue.MilestoneID = form.Milestone | ||||||
| 		assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees) | 		assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -274,7 +274,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() { | 	if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		ctx.Status(403) | 		ctx.Status(403) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -288,7 +288,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
|  |  | ||||||
| 	// Update the deadline | 	// Update the deadline | ||||||
| 	var deadlineUnix util.TimeStamp | 	var deadlineUnix util.TimeStamp | ||||||
| 	if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.IsWriter() { | 	if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | 		deadlineUnix = util.TimeStamp(form.Deadline.Unix()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -305,8 +305,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
| 	// Pass one or more user logins to replace the set of assignees on this Issue. | 	// Pass one or more user logins to replace the set of assignees on this Issue. | ||||||
| 	// Send an empty array ([]) to clear all assignees from the Issue. | 	// Send an empty array ([]) to clear all assignees from the Issue. | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() && (form.Assignees != nil || form.Assignee != nil) { | 	if ctx.Repo.CanWrite(models.UnitTypeIssues) && (form.Assignees != nil || form.Assignee != nil) { | ||||||
|  |  | ||||||
| 		oneAssignee := "" | 		oneAssignee := "" | ||||||
| 		if form.Assignee != nil { | 		if form.Assignee != nil { | ||||||
| 			oneAssignee = *form.Assignee | 			oneAssignee = *form.Assignee | ||||||
| @@ -319,7 +318,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() && form.Milestone != nil && | 	if ctx.Repo.CanWrite(models.UnitTypeIssues) && form.Milestone != nil && | ||||||
| 		issue.MilestoneID != *form.Milestone { | 		issue.MilestoneID != *form.Milestone { | ||||||
| 		oldMilestoneID := issue.MilestoneID | 		oldMilestoneID := issue.MilestoneID | ||||||
| 		issue.MilestoneID = *form.Milestone | 		issue.MilestoneID = *form.Milestone | ||||||
| @@ -403,7 +402,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.Repo.IsWriter() { | 	if !ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		ctx.Status(403) | 		ctx.Status(403) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2016 The Gogs Authors. All rights reserved. | // Copyright 2016 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -90,11 +91,6 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/LabelList" | 	//     "$ref": "#/responses/LabelList" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrIssueNotExist(err) { | 		if models.IsErrIssueNotExist(err) { | ||||||
| @@ -105,6 +101,11 @@ func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | 	labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "GetLabelsInRepoByIDs", err) | 		ctx.Error(500, "GetLabelsInRepoByIDs", err) | ||||||
| @@ -162,11 +163,6 @@ func DeleteIssueLabel(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrIssueNotExist(err) { | 		if models.IsErrIssueNotExist(err) { | ||||||
| @@ -177,6 +173,11 @@ func DeleteIssueLabel(ctx *context.APIContext) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | 	label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrLabelNotExist(err) { | 		if models.IsErrLabelNotExist(err) { | ||||||
| @@ -228,11 +229,6 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/LabelList" | 	//     "$ref": "#/responses/LabelList" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrIssueNotExist(err) { | 		if models.IsErrIssueNotExist(err) { | ||||||
| @@ -243,6 +239,11 @@ func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | 	labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "GetLabelsInRepoByIDs", err) | 		ctx.Error(500, "GetLabelsInRepoByIDs", err) | ||||||
| @@ -294,11 +295,6 @@ func ClearIssueLabels(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrIssueNotExist(err) { | 		if models.IsErrIssueNotExist(err) { | ||||||
| @@ -309,6 +305,11 @@ func ClearIssueLabels(ctx *context.APIContext) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
|  | 		ctx.Status(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if err := issue.ClearLabels(ctx.User); err != nil { | 	if err := issue.ClearLabels(ctx.User); err != nil { | ||||||
| 		ctx.Error(500, "ClearLabels", err) | 		ctx.Error(500, "ClearLabels", err) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2016 The Gogs Authors. All rights reserved. | // Copyright 2016 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -123,11 +124,6 @@ func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "201": | 	//   "201": | ||||||
| 	//     "$ref": "#/responses/Label" | 	//     "$ref": "#/responses/Label" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	label := &models.Label{ | 	label := &models.Label{ | ||||||
| 		Name:   form.Name, | 		Name:   form.Name, | ||||||
| 		Color:  form.Color, | 		Color:  form.Color, | ||||||
| @@ -173,11 +169,6 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/Label" | 	//     "$ref": "#/responses/Label" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | 	label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrLabelNotExist(err) { | 		if models.IsErrLabelNotExist(err) { | ||||||
| @@ -226,11 +217,6 @@ func DeleteLabel(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil { | 	if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil { | ||||||
| 		ctx.Error(500, "DeleteLabel", err) | 		ctx.Error(500, "DeleteLabel", err) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -351,7 +351,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 	pr.LoadIssue() | 	pr.LoadIssue() | ||||||
| 	issue := pr.Issue | 	issue := pr.Issue | ||||||
|  |  | ||||||
| 	if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() { | 	if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypePullRequests) { | ||||||
| 		ctx.Status(403) | 		ctx.Status(403) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -382,7 +382,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 	// Pass one or more user logins to replace the set of assignees on this Issue. | 	// Pass one or more user logins to replace the set of assignees on this Issue. | ||||||
| 	// Send an empty array ([]) to clear all assignees from the Issue. | 	// Send an empty array ([]) to clear all assignees from the Issue. | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() && (form.Assignees != nil || len(form.Assignee) > 0) { | 	if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Assignees != nil || len(form.Assignee) > 0) { | ||||||
|  |  | ||||||
| 		err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User) | 		err = models.UpdateAPIAssignee(issue, form.Assignee, form.Assignees, ctx.User) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -395,7 +395,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() && form.Milestone != 0 && | 	if ctx.Repo.CanWrite(models.UnitTypePullRequests) && form.Milestone != 0 && | ||||||
| 		issue.MilestoneID != form.Milestone { | 		issue.MilestoneID != form.Milestone { | ||||||
| 		oldMilestoneID := issue.MilestoneID | 		oldMilestoneID := issue.MilestoneID | ||||||
| 		issue.MilestoneID = form.Milestone | 		issue.MilestoneID = form.Milestone | ||||||
| @@ -405,7 +405,7 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() && (form.Labels != nil && len(form.Labels) > 0) { | 	if ctx.Repo.CanWrite(models.UnitTypePullRequests) && (form.Labels != nil && len(form.Labels) > 0) { | ||||||
| 		labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | 		labels, err := models.GetLabelsInRepoByIDs(ctx.Repo.Repository.ID, form.Labels) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Error(500, "GetLabelsInRepoByIDsError", err) | 			ctx.Error(500, "GetLabelsInRepoByIDsError", err) | ||||||
| @@ -663,7 +663,12 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.User.IsWriterOfRepo(headRepo) && !ctx.User.IsAdmin { | 	perm, err := models.GetUserRepoPermission(headRepo, ctx.User) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 		return nil, nil, nil, nil, "", "" | ||||||
|  | 	} | ||||||
|  | 	if !perm.CanWrite(models.UnitTypeCode) { | ||||||
| 		log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID) | 		log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID) | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return nil, nil, nil, nil, "", "" | 		return nil, nil, nil, nil, "", "" | ||||||
|   | |||||||
| @@ -122,10 +122,6 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "201": | 	//   "201": | ||||||
| 	//     "$ref": "#/responses/Release" | 	//     "$ref": "#/responses/Release" | ||||||
| 	if ctx.Repo.AccessMode < models.AccessModeWrite { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName) | 	rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if !models.IsErrReleaseNotExist(err) { | 		if !models.IsErrReleaseNotExist(err) { | ||||||
| @@ -209,10 +205,6 @@ func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/Release" | 	//     "$ref": "#/responses/Release" | ||||||
| 	if ctx.Repo.AccessMode < models.AccessModeWrite { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	id := ctx.ParamsInt64(":id") | 	id := ctx.ParamsInt64(":id") | ||||||
| 	rel, err := models.GetReleaseByID(id) | 	rel, err := models.GetReleaseByID(id) | ||||||
| 	if err != nil && !models.IsErrReleaseNotExist(err) { | 	if err != nil && !models.IsErrReleaseNotExist(err) { | ||||||
| @@ -285,10 +277,6 @@ func DeleteRelease(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "204": | 	//   "204": | ||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	if ctx.Repo.AccessMode < models.AccessModeWrite { |  | ||||||
| 		ctx.Status(403) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	id := ctx.ParamsInt64(":id") | 	id := ctx.ParamsInt64(":id") | ||||||
| 	rel, err := models.GetReleaseByID(id) | 	rel, err := models.GetReleaseByID(id) | ||||||
| 	if err != nil && !models.IsErrReleaseNotExist(err) { | 	if err != nil && !models.IsErrReleaseNotExist(err) { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -183,11 +184,6 @@ func Search(ctx *context.APIContext) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var userID int64 |  | ||||||
| 	if ctx.IsSigned { |  | ||||||
| 		userID = ctx.User.ID |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	results := make([]*api.Repository, len(repos)) | 	results := make([]*api.Repository, len(repos)) | ||||||
| 	for i, repo := range repos { | 	for i, repo := range repos { | ||||||
| 		if err = repo.GetOwner(); err != nil { | 		if err = repo.GetOwner(); err != nil { | ||||||
| @@ -197,7 +193,7 @@ func Search(ctx *context.APIContext) { | |||||||
| 			}) | 			}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		accessMode, err := models.AccessLevel(userID, repo) | 		accessMode, err := models.AccessLevel(ctx.User, repo) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.JSON(500, api.SearchError{ | 			ctx.JSON(500, api.SearchError{ | ||||||
| 				OK:    false, | 				OK:    false, | ||||||
| @@ -469,15 +465,15 @@ func GetByID(ctx *context.APIContext) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	access, err := models.AccessLevel(ctx.User.ID, repo) | 	perm, err := models.GetUserRepoPermission(repo, ctx.User) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "AccessLevel", err) | 		ctx.Error(500, "AccessLevel", err) | ||||||
| 		return | 		return | ||||||
| 	} else if access < models.AccessModeRead { | 	} else if !perm.HasAccess() { | ||||||
| 		ctx.Status(404) | 		ctx.Status(404) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(200, repo.APIFormat(access)) | 	ctx.JSON(200, repo.APIFormat(perm.AccessMode)) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Delete one repository | // Delete one repository | ||||||
| @@ -503,10 +499,6 @@ func Delete(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	//   "403": | 	//   "403": | ||||||
| 	//     "$ref": "#/responses/forbidden" | 	//     "$ref": "#/responses/forbidden" | ||||||
| 	if !ctx.Repo.IsAdmin() { |  | ||||||
| 		ctx.Error(403, "", "Must have admin rights") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	owner := ctx.Repo.Owner | 	owner := ctx.Repo.Owner | ||||||
| 	repo := ctx.Repo.Repository | 	repo := ctx.Repo.Repository | ||||||
|  |  | ||||||
| @@ -553,7 +545,7 @@ func MirrorSync(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/empty" | 	//     "$ref": "#/responses/empty" | ||||||
| 	repo := ctx.Repo.Repository | 	repo := ctx.Repo.Repository | ||||||
|  |  | ||||||
| 	if !ctx.Repo.IsWriter() { | 	if !ctx.Repo.CanWrite(models.UnitTypeCode) { | ||||||
| 		ctx.Error(403, "MirrorSync", "Must have write access") | 		ctx.Error(403, "MirrorSync", "Must have write access") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,13 +17,10 @@ func listUserRepos(ctx *context.APIContext, u *models.User, private bool) { | |||||||
| 		ctx.Error(500, "GetUserRepositories", err) | 		ctx.Error(500, "GetUserRepositories", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	apiRepos := make([]*api.Repository, 0, len(repos)) | 	apiRepos := make([]*api.Repository, 0, len(repos)) | ||||||
| 	var ctxUserID int64 |  | ||||||
| 	if ctx.User != nil { |  | ||||||
| 		ctxUserID = ctx.User.ID |  | ||||||
| 	} |  | ||||||
| 	for i := range repos { | 	for i := range repos { | ||||||
| 		access, err := models.AccessLevel(ctxUserID, repos[i]) | 		access, err := models.AccessLevel(ctx.User, repos[i]) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.Error(500, "AccessLevel", err) | 			ctx.Error(500, "AccessLevel", err) | ||||||
| 			return | 			return | ||||||
|   | |||||||
| @@ -13,15 +13,15 @@ import ( | |||||||
|  |  | ||||||
| // getStarredRepos returns the repos that the user with the specified userID has | // getStarredRepos returns the repos that the user with the specified userID has | ||||||
| // starred | // starred | ||||||
| func getStarredRepos(userID int64, private bool) ([]*api.Repository, error) { | func getStarredRepos(user *models.User, private bool) ([]*api.Repository, error) { | ||||||
| 	starredRepos, err := models.GetStarredRepos(userID, private) | 	starredRepos, err := models.GetStarredRepos(user.ID, private) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repos := make([]*api.Repository, len(starredRepos)) | 	repos := make([]*api.Repository, len(starredRepos)) | ||||||
| 	for i, starred := range starredRepos { | 	for i, starred := range starredRepos { | ||||||
| 		access, err := models.AccessLevel(userID, starred) | 		access, err := models.AccessLevel(user, starred) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -48,7 +48,7 @@ func GetStarredRepos(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/RepositoryList" | 	//     "$ref": "#/responses/RepositoryList" | ||||||
| 	user := GetUserByParams(ctx) | 	user := GetUserByParams(ctx) | ||||||
| 	private := user.ID == ctx.User.ID | 	private := user.ID == ctx.User.ID | ||||||
| 	repos, err := getStarredRepos(user.ID, private) | 	repos, err := getStarredRepos(user, private) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "getStarredRepos", err) | 		ctx.Error(500, "getStarredRepos", err) | ||||||
| 	} | 	} | ||||||
| @@ -65,7 +65,7 @@ func GetMyStarredRepos(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/RepositoryList" | 	//     "$ref": "#/responses/RepositoryList" | ||||||
| 	repos, err := getStarredRepos(ctx.User.ID, true) | 	repos, err := getStarredRepos(ctx.User, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "getStarredRepos", err) | 		ctx.Error(500, "getStarredRepos", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -14,15 +14,15 @@ import ( | |||||||
|  |  | ||||||
| // getWatchedRepos returns the repos that the user with the specified userID is | // getWatchedRepos returns the repos that the user with the specified userID is | ||||||
| // watching | // watching | ||||||
| func getWatchedRepos(userID int64, private bool) ([]*api.Repository, error) { | func getWatchedRepos(user *models.User, private bool) ([]*api.Repository, error) { | ||||||
| 	watchedRepos, err := models.GetWatchedRepos(userID, private) | 	watchedRepos, err := models.GetWatchedRepos(user.ID, private) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	repos := make([]*api.Repository, len(watchedRepos)) | 	repos := make([]*api.Repository, len(watchedRepos)) | ||||||
| 	for i, watched := range watchedRepos { | 	for i, watched := range watchedRepos { | ||||||
| 		access, err := models.AccessLevel(userID, watched) | 		access, err := models.AccessLevel(user, watched) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -49,7 +49,7 @@ func GetWatchedRepos(ctx *context.APIContext) { | |||||||
| 	//     "$ref": "#/responses/RepositoryList" | 	//     "$ref": "#/responses/RepositoryList" | ||||||
| 	user := GetUserByParams(ctx) | 	user := GetUserByParams(ctx) | ||||||
| 	private := user.ID == ctx.User.ID | 	private := user.ID == ctx.User.ID | ||||||
| 	repos, err := getWatchedRepos(user.ID, private) | 	repos, err := getWatchedRepos(user, private) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "getWatchedRepos", err) | 		ctx.Error(500, "getWatchedRepos", err) | ||||||
| 	} | 	} | ||||||
| @@ -66,7 +66,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) { | |||||||
| 	// responses: | 	// responses: | ||||||
| 	//   "200": | 	//   "200": | ||||||
| 	//     "$ref": "#/responses/RepositoryList" | 	//     "$ref": "#/responses/RepositoryList" | ||||||
| 	repos, err := getWatchedRepos(ctx.User.ID, true) | 	repos, err := getWatchedRepos(ctx.User, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.Error(500, "getWatchedRepos", err) | 		ctx.Error(500, "getWatchedRepos", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -38,27 +38,6 @@ func GetRepositoryByOwnerAndName(ctx *macaron.Context) { | |||||||
| 	ctx.JSON(200, repo) | 	ctx.JSON(200, repo) | ||||||
| } | } | ||||||
|  |  | ||||||
| //AccessLevel chainload to models.AccessLevel |  | ||||||
| func AccessLevel(ctx *macaron.Context) { |  | ||||||
| 	repoID := ctx.ParamsInt64(":repoid") |  | ||||||
| 	userID := ctx.ParamsInt64(":userid") |  | ||||||
| 	repo, err := models.GetRepositoryByID(repoID) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.JSON(500, map[string]interface{}{ |  | ||||||
| 			"err": err.Error(), |  | ||||||
| 		}) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	al, err := models.AccessLevel(userID, repo) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.JSON(500, map[string]interface{}{ |  | ||||||
| 			"err": err.Error(), |  | ||||||
| 		}) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	ctx.JSON(200, al) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //CheckUnitUser chainload to models.CheckUnitUser | //CheckUnitUser chainload to models.CheckUnitUser | ||||||
| func CheckUnitUser(ctx *macaron.Context) { | func CheckUnitUser(ctx *macaron.Context) { | ||||||
| 	repoID := ctx.ParamsInt64(":repoid") | 	repoID := ctx.ParamsInt64(":repoid") | ||||||
| @@ -70,11 +49,27 @@ func CheckUnitUser(ctx *macaron.Context) { | |||||||
| 		}) | 		}) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if repo.CheckUnitUser(userID, ctx.QueryBool("isAdmin"), models.UnitType(ctx.QueryInt("unitType"))) { |  | ||||||
| 		ctx.PlainText(200, []byte("success")) | 	var user *models.User | ||||||
|  | 	if userID > 0 { | ||||||
|  | 		user, err = models.GetUserByID(userID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.JSON(500, map[string]interface{}{ | ||||||
|  | 				"err": err.Error(), | ||||||
|  | 			}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	ctx.PlainText(404, []byte("no access")) | 	} | ||||||
|  |  | ||||||
|  | 	perm, err := models.GetUserRepoPermission(repo, user) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.JSON(500, map[string]interface{}{ | ||||||
|  | 			"err": err.Error(), | ||||||
|  | 		}) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx.JSON(200, perm.UnitAccessMode(models.UnitType(ctx.QueryInt("unitType")))) | ||||||
| } | } | ||||||
|  |  | ||||||
| // RegisterRoutes registers all internal APIs routes to web application. | // RegisterRoutes registers all internal APIs routes to web application. | ||||||
| @@ -85,7 +80,6 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		m.Get("/ssh/:id/user", GetUserByKeyID) | 		m.Get("/ssh/:id/user", GetUserByKeyID) | ||||||
| 		m.Post("/ssh/:id/update", UpdatePublicKey) | 		m.Post("/ssh/:id/update", UpdatePublicKey) | ||||||
| 		m.Post("/repositories/:repoid/keys/:keyid/update", UpdateDeployKey) | 		m.Post("/repositories/:repoid/keys/:keyid/update", UpdateDeployKey) | ||||||
| 		m.Get("/repositories/:repoid/user/:userid/accesslevel", AccessLevel) |  | ||||||
| 		m.Get("/repositories/:repoid/user/:userid/checkunituser", CheckUnitUser) | 		m.Get("/repositories/:repoid/user/:userid/checkunituser", CheckUnitUser) | ||||||
| 		m.Get("/repositories/:repoid/has-keys/:keyid", HasDeployKey) | 		m.Get("/repositories/:repoid/has-keys/:keyid", HasDeployKey) | ||||||
| 		m.Post("/push/update", PushUpdate) | 		m.Post("/push/update", PushUpdate) | ||||||
|   | |||||||
| @@ -45,9 +45,9 @@ func Activity(ctx *context.Context) { | |||||||
|  |  | ||||||
| 	var err error | 	var err error | ||||||
| 	if ctx.Data["Activity"], err = models.GetActivityStats(ctx.Repo.Repository.ID, timeFrom, | 	if ctx.Data["Activity"], err = models.GetActivityStats(ctx.Repo.Repository.ID, timeFrom, | ||||||
| 		ctx.Repo.Repository.UnitEnabled(models.UnitTypeReleases), | 		ctx.Repo.CanRead(models.UnitTypeReleases), | ||||||
| 		ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues), | 		ctx.Repo.CanRead(models.UnitTypeIssues), | ||||||
| 		ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests)); err != nil { | 		ctx.Repo.CanRead(models.UnitTypePullRequests)); err != nil { | ||||||
| 		ctx.ServerError("GetActivityStats", err) | 		ctx.ServerError("GetActivityStats", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -33,7 +34,7 @@ func Branches(ctx *context.Context) { | |||||||
| 	ctx.Data["Title"] = "Branches" | 	ctx.Data["Title"] = "Branches" | ||||||
| 	ctx.Data["IsRepoToolbarBranches"] = true | 	ctx.Data["IsRepoToolbarBranches"] = true | ||||||
| 	ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch | 	ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch | ||||||
| 	ctx.Data["IsWriter"] = ctx.Repo.IsWriter() | 	ctx.Data["IsWriter"] = ctx.Repo.CanWrite(models.UnitTypeCode) | ||||||
| 	ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror | 	ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror | ||||||
| 	ctx.Data["PageIsViewCode"] = true | 	ctx.Data["PageIsViewCode"] = true | ||||||
| 	ctx.Data["PageIsBranches"] = true | 	ctx.Data["PageIsBranches"] = true | ||||||
| @@ -161,7 +162,7 @@ func loadBranches(ctx *context.Context) []*Branch { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.IsWriter() { | 	if ctx.Repo.CanWrite(models.UnitTypeCode) { | ||||||
| 		deletedBranches, err := getDeletedBranches(ctx) | 		deletedBranches, err := getDeletedBranches(ctx) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.ServerError("getDeletedBranches", err) | 			ctx.ServerError("getDeletedBranches", err) | ||||||
|   | |||||||
| @@ -182,38 +182,21 @@ func HTTP(ctx *context.Context) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !isPublicPull { | 		perm, err := models.GetUserRepoPermission(repo, authUser) | ||||||
| 			has, err := models.HasAccess(authUser.ID, repo, accessMode) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 				ctx.ServerError("HasAccess", err) | 			ctx.ServerError("GetUserRepoPermission", err) | ||||||
| 				return |  | ||||||
| 			} else if !has { |  | ||||||
| 				if accessMode == models.AccessModeRead { |  | ||||||
| 					has, err = models.HasAccess(authUser.ID, repo, models.AccessModeWrite) |  | ||||||
| 					if err != nil { |  | ||||||
| 						ctx.ServerError("HasAccess2", err) |  | ||||||
| 						return |  | ||||||
| 					} else if !has { |  | ||||||
| 						ctx.HandleText(http.StatusForbidden, "User permission denied") |  | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 				} else { |  | ||||||
|  | 		if !perm.CanAccess(accessMode, unitType) { | ||||||
| 			ctx.HandleText(http.StatusForbidden, "User permission denied") | 			ctx.HandleText(http.StatusForbidden, "User permission denied") | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		if !isPull && repo.IsMirror { | 		if !isPull && repo.IsMirror { | ||||||
| 			ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") | 			ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !repo.CheckUnitUser(authUser.ID, authUser.IsAdmin, unitType) { |  | ||||||
| 			ctx.HandleText(http.StatusForbidden, fmt.Sprintf("User %s does not have allowed access to repository %s 's code", |  | ||||||
| 				authUser.Name, repo.RepoPath())) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		environ = []string{ | 		environ = []string{ | ||||||
| 			models.EnvRepoUsername + "=" + username, | 			models.EnvRepoUsername + "=" + username, | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -63,8 +64,8 @@ var ( | |||||||
|  |  | ||||||
| // MustEnableIssues check if repository enable internal issues | // MustEnableIssues check if repository enable internal issues | ||||||
| func MustEnableIssues(ctx *context.Context) { | func MustEnableIssues(ctx *context.Context) { | ||||||
| 	if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) && | 	if !ctx.Repo.CanRead(models.UnitTypeIssues) && | ||||||
| 		!ctx.Repo.Repository.UnitEnabled(models.UnitTypeExternalTracker) { | 		!ctx.Repo.CanRead(models.UnitTypeExternalTracker) { | ||||||
| 		ctx.NotFound("MustEnableIssues", nil) | 		ctx.NotFound("MustEnableIssues", nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -76,9 +77,9 @@ func MustEnableIssues(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // MustAllowPulls check if repository enable pull requests | // MustAllowPulls check if repository enable pull requests and user have right to do that | ||||||
| func MustAllowPulls(ctx *context.Context) { | func MustAllowPulls(ctx *context.Context) { | ||||||
| 	if !ctx.Repo.Repository.AllowsPulls() { | 	if !ctx.Repo.Repository.CanEnablePulls() || !ctx.Repo.CanRead(models.UnitTypePullRequests) { | ||||||
| 		ctx.NotFound("MustAllowPulls", nil) | 		ctx.NotFound("MustAllowPulls", nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -280,7 +281,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos | |||||||
|  |  | ||||||
| // RetrieveRepoMetas find all the meta information of a repository | // RetrieveRepoMetas find all the meta information of a repository | ||||||
| func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label { | func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label { | ||||||
| 	if !ctx.Repo.IsWriter() { | 	if !ctx.Repo.CanWrite(models.UnitTypeIssues) { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -369,7 +370,7 @@ func NewIssue(ctx *context.Context) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ValidateRepoMetas check and returns repository's meta informations | // ValidateRepoMetas check and returns repository's meta informations | ||||||
| func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64, []int64, int64) { | func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull bool) ([]int64, []int64, int64) { | ||||||
| 	var ( | 	var ( | ||||||
| 		repo = ctx.Repo.Repository | 		repo = ctx.Repo.Repository | ||||||
| 		err  error | 		err  error | ||||||
| @@ -380,10 +381,6 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64 | |||||||
| 		return nil, nil, 0 | 		return nil, nil, 0 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.Repo.IsWriter() { |  | ||||||
| 		return nil, nil, 0 |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var labelIDs []int64 | 	var labelIDs []int64 | ||||||
| 	hasSelected := false | 	hasSelected := false | ||||||
| 	// Check labels. | 	// Check labels. | ||||||
| @@ -427,9 +424,19 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm) ([]int64 | |||||||
|  |  | ||||||
| 		// Check if the passed assignees actually exists and has write access to the repo | 		// Check if the passed assignees actually exists and has write access to the repo | ||||||
| 		for _, aID := range assigneeIDs { | 		for _, aID := range assigneeIDs { | ||||||
| 			_, err = repo.GetUserIfHasWriteAccess(aID) | 			user, err := models.GetUserByID(aID) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				ctx.ServerError("GetUserIfHasWriteAccess", err) | 				ctx.ServerError("GetUserByID", err) | ||||||
|  | 				return nil, nil, 0 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			perm, err := models.GetUserRepoPermission(repo, user) | ||||||
|  | 			if err != nil { | ||||||
|  | 				ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 				return nil, nil, 0 | ||||||
|  | 			} | ||||||
|  | 			if !perm.CanWriteIssuesOrPulls(isPull) { | ||||||
|  | 				ctx.ServerError("CanWriteIssuesOrPulls", fmt.Errorf("No permission for %s", user.Name)) | ||||||
| 				return nil, nil, 0 | 				return nil, nil, 0 | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -458,7 +465,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { | |||||||
| 		attachments []string | 		attachments []string | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form) | 	labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, false) | ||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -498,31 +505,23 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { | |||||||
|  |  | ||||||
| // commentTag returns the CommentTag for a comment in/with the given repo, poster and issue | // commentTag returns the CommentTag for a comment in/with the given repo, poster and issue | ||||||
| func commentTag(repo *models.Repository, poster *models.User, issue *models.Issue) (models.CommentTag, error) { | func commentTag(repo *models.Repository, poster *models.User, issue *models.Issue) (models.CommentTag, error) { | ||||||
| 	if repo.IsOwnedBy(poster.ID) { | 	perm, err := models.GetUserRepoPermission(repo, poster) | ||||||
| 		return models.CommentTagOwner, nil |  | ||||||
| 	} else if repo.Owner.IsOrganization() { |  | ||||||
| 		isOwner, err := repo.Owner.IsOwnedBy(poster.ID) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return models.CommentTagNone, err | 		return models.CommentTagNone, err | ||||||
| 		} else if isOwner { | 	} | ||||||
|  | 	if perm.IsOwner() { | ||||||
| 		return models.CommentTagOwner, nil | 		return models.CommentTagOwner, nil | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if poster.IsWriterOfRepo(repo) { |  | ||||||
| 		return models.CommentTagWriter, nil |  | ||||||
| 	} else if poster.ID == issue.PosterID { | 	} else if poster.ID == issue.PosterID { | ||||||
| 		return models.CommentTagPoster, nil | 		return models.CommentTagPoster, nil | ||||||
|  | 	} else if perm.CanWrite(models.UnitTypeCode) { | ||||||
|  | 		return models.CommentTagWriter, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return models.CommentTagNone, nil | 	return models.CommentTagNone, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // ViewIssue render issue view page | // ViewIssue render issue view page | ||||||
| func ViewIssue(ctx *context.Context) { | func ViewIssue(ctx *context.Context) { | ||||||
| 	ctx.Data["RequireHighlightJS"] = true |  | ||||||
| 	ctx.Data["RequireDropzone"] = true |  | ||||||
| 	ctx.Data["RequireTribute"] = true |  | ||||||
| 	renderAttachmentSettings(ctx) |  | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if models.IsErrIssueNotExist(err) { | 		if models.IsErrIssueNotExist(err) { | ||||||
| @@ -532,25 +531,6 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) |  | ||||||
|  |  | ||||||
| 	var iw *models.IssueWatch |  | ||||||
| 	var exists bool |  | ||||||
| 	if ctx.User != nil { |  | ||||||
| 		iw, exists, err = models.GetIssueWatch(ctx.User.ID, issue.ID) |  | ||||||
| 		if err != nil { |  | ||||||
| 			ctx.ServerError("GetIssueWatch", err) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		if !exists { |  | ||||||
| 			iw = &models.IssueWatch{ |  | ||||||
| 				UserID:     ctx.User.ID, |  | ||||||
| 				IssueID:    issue.ID, |  | ||||||
| 				IsWatching: models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID), |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	ctx.Data["IssueWatch"] = iw |  | ||||||
|  |  | ||||||
| 	// Make sure type and URL matches. | 	// Make sure type and URL matches. | ||||||
| 	if ctx.Params(":type") == "issues" && issue.IsPull { | 	if ctx.Params(":type") == "issues" && issue.IsPull { | ||||||
| @@ -576,6 +556,31 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 		ctx.Data["PageIsIssueList"] = true | 		ctx.Data["PageIsIssueList"] = true | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	ctx.Data["RequireHighlightJS"] = true | ||||||
|  | 	ctx.Data["RequireDropzone"] = true | ||||||
|  | 	ctx.Data["RequireTribute"] = true | ||||||
|  | 	renderAttachmentSettings(ctx) | ||||||
|  |  | ||||||
|  | 	ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | ||||||
|  |  | ||||||
|  | 	var iw *models.IssueWatch | ||||||
|  | 	var exists bool | ||||||
|  | 	if ctx.User != nil { | ||||||
|  | 		iw, exists, err = models.GetIssueWatch(ctx.User.ID, issue.ID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetIssueWatch", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if !exists { | ||||||
|  | 			iw = &models.IssueWatch{ | ||||||
|  | 				UserID:     ctx.User.ID, | ||||||
|  | 				IssueID:    issue.ID, | ||||||
|  | 				IsWatching: models.IsWatching(ctx.User.ID, ctx.Repo.Repository.ID), | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	ctx.Data["IssueWatch"] = iw | ||||||
|  |  | ||||||
| 	issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink, | 	issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink, | ||||||
| 		ctx.Repo.Repository.ComposeMetas())) | 		ctx.Repo.Repository.ComposeMetas())) | ||||||
|  |  | ||||||
| @@ -616,7 +621,7 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 	ctx.Data["Labels"] = labels | 	ctx.Data["Labels"] = labels | ||||||
|  |  | ||||||
| 	// Check milestone and assignee. | 	// Check milestone and assignee. | ||||||
| 	if ctx.Repo.IsWriter() { | 	if ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { | ||||||
| 		RetrieveRepoMilestonesAndAssignees(ctx, repo) | 		RetrieveRepoMilestonesAndAssignees(ctx, repo) | ||||||
| 		if ctx.Written() { | 		if ctx.Written() { | ||||||
| 			return | 			return | ||||||
| @@ -761,7 +766,13 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 		if ctx.IsSigned { | 		if ctx.IsSigned { | ||||||
| 			if err := pull.GetHeadRepo(); err != nil { | 			if err := pull.GetHeadRepo(); err != nil { | ||||||
| 				log.Error(4, "GetHeadRepo: %v", err) | 				log.Error(4, "GetHeadRepo: %v", err) | ||||||
| 			} else if pull.HeadRepo != nil && pull.HeadBranch != pull.HeadRepo.DefaultBranch && ctx.User.IsWriterOfRepo(pull.HeadRepo) { | 			} else if pull.HeadRepo != nil && pull.HeadBranch != pull.HeadRepo.DefaultBranch { | ||||||
|  | 				perm, err := models.GetUserRepoPermission(pull.HeadRepo, ctx.User) | ||||||
|  | 				if err != nil { | ||||||
|  | 					ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				if perm.CanWrite(models.UnitTypeCode) { | ||||||
| 					// Check if branch is not protected | 					// Check if branch is not protected | ||||||
| 					if protected, err := pull.HeadRepo.IsProtectedBranch(pull.HeadBranch, ctx.User); err != nil { | 					if protected, err := pull.HeadRepo.IsProtectedBranch(pull.HeadBranch, ctx.User); err != nil { | ||||||
| 						log.Error(4, "IsProtectedBranch: %v", err) | 						log.Error(4, "IsProtectedBranch: %v", err) | ||||||
| @@ -771,6 +782,7 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		prUnit, err := repo.GetUnit(models.UnitTypePullRequests) | 		prUnit, err := repo.GetUnit(models.UnitTypePullRequests) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -779,7 +791,7 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 		} | 		} | ||||||
| 		prConfig := prUnit.PullRequestsConfig() | 		prConfig := prUnit.PullRequestsConfig() | ||||||
|  |  | ||||||
| 		ctx.Data["AllowMerge"] = ctx.Data["IsRepositoryWriter"] | 		ctx.Data["AllowMerge"] = ctx.Repo.CanWrite(models.UnitTypeCode) | ||||||
| 		if err := pull.CheckUserAllowedToMerge(ctx.User); err != nil { | 		if err := pull.CheckUserAllowedToMerge(ctx.User); err != nil { | ||||||
| 			if !models.IsErrNotAllowedToMerge(err) { | 			if !models.IsErrNotAllowedToMerge(err) { | ||||||
| 				ctx.ServerError("CheckUserAllowedToMerge", err) | 				ctx.ServerError("CheckUserAllowedToMerge", err) | ||||||
| @@ -818,8 +830,9 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 	ctx.Data["NumParticipants"] = len(participants) | 	ctx.Data["NumParticipants"] = len(participants) | ||||||
| 	ctx.Data["Issue"] = issue | 	ctx.Data["Issue"] = issue | ||||||
| 	ctx.Data["ReadOnly"] = true | 	ctx.Data["ReadOnly"] = true | ||||||
| 	ctx.Data["IsIssueOwner"] = ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID)) |  | ||||||
| 	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) | 	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) | ||||||
|  | 	ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) | ||||||
|  | 	ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) | ||||||
| 	ctx.HTML(200, tplIssueView) | 	ctx.HTML(200, tplIssueView) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -842,8 +855,8 @@ func GetActionIssue(ctx *context.Context) *models.Issue { | |||||||
| } | } | ||||||
|  |  | ||||||
| func checkIssueRights(ctx *context.Context, issue *models.Issue) { | func checkIssueRights(ctx *context.Context, issue *models.Issue) { | ||||||
| 	if issue.IsPull && !ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) || | 	if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypePullRequests) || | ||||||
| 		!issue.IsPull && !ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) { | 		!issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) { | ||||||
| 		ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) | 		ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -868,8 +881,8 @@ func getActionIssues(ctx *context.Context) []*models.Issue { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	// Check access rights for all issues | 	// Check access rights for all issues | ||||||
| 	issueUnitEnabled := ctx.Repo.Repository.UnitEnabled(models.UnitTypeIssues) | 	issueUnitEnabled := ctx.Repo.CanRead(models.UnitTypeIssues) | ||||||
| 	prUnitEnabled := ctx.Repo.Repository.UnitEnabled(models.UnitTypePullRequests) | 	prUnitEnabled := ctx.Repo.CanRead(models.UnitTypePullRequests) | ||||||
| 	for _, issue := range issues { | 	for _, issue := range issues { | ||||||
| 		if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { | 		if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { | ||||||
| 			ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) | 			ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) | ||||||
| @@ -890,7 +903,7 @@ func UpdateIssueTitle(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter()) { | 	if !ctx.IsSigned || (!issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { | ||||||
| 		ctx.Error(403) | 		ctx.Error(403) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -918,7 +931,7 @@ func UpdateIssueContent(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.IsWriter()) { | 	if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { | ||||||
| 		ctx.Error(403) | 		ctx.Error(403) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -1037,6 +1050,11 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) { | ||||||
|  | 		ctx.Error(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	var attachments []string | 	var attachments []string | ||||||
| 	if setting.AttachmentEnabled { | 	if setting.AttachmentEnabled { | ||||||
| 		attachments = form.Files | 		attachments = form.Files | ||||||
| @@ -1051,7 +1069,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { | |||||||
| 	var comment *models.Comment | 	var comment *models.Comment | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		// Check if issue admin/poster changes the status of issue. | 		// Check if issue admin/poster changes the status of issue. | ||||||
| 		if (ctx.Repo.IsWriter() || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) && | 		if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.User.ID))) && | ||||||
| 			(form.Status == "reopen" || form.Status == "close") && | 			(form.Status == "reopen" || form.Status == "close") && | ||||||
| 			!(issue.IsPull && issue.PullRequest.HasMerged) { | 			!(issue.IsPull && issue.PullRequest.HasMerged) { | ||||||
|  |  | ||||||
| @@ -1140,7 +1158,12 @@ func UpdateCommentContent(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) { | 	if err := comment.LoadIssue(); err != nil { | ||||||
|  | 		ctx.NotFoundOrServerError("LoadIssue", models.IsErrIssueNotExist, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | ||||||
| 		ctx.Error(403) | 		ctx.Error(403) | ||||||
| 		return | 		return | ||||||
| 	} else if comment.Type != models.CommentTypeComment && comment.Type != models.CommentTypeCode { | 	} else if comment.Type != models.CommentTypeComment && comment.Type != models.CommentTypeCode { | ||||||
| @@ -1174,7 +1197,12 @@ func DeleteComment(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.IsAdmin()) { | 	if err := comment.LoadIssue(); err != nil { | ||||||
|  | 		ctx.NotFoundOrServerError("LoadIssue", models.IsErrIssueNotExist, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | ||||||
| 		ctx.Error(403) | 		ctx.Error(403) | ||||||
| 		return | 		return | ||||||
| 	} else if comment.Type != models.CommentTypeComment && comment.Type != models.CommentTypeCode { | 	} else if comment.Type != models.CommentTypeComment && comment.Type != models.CommentTypeCode { | ||||||
| @@ -1417,6 +1445,11 @@ func ChangeIssueReaction(ctx *context.Context, form auth.ReactionForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)) { | ||||||
|  | 		ctx.Error(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if ctx.HasError() { | 	if ctx.HasError() { | ||||||
| 		ctx.ServerError("ChangeIssueReaction", errors.New(ctx.GetErrMsg())) | 		ctx.ServerError("ChangeIssueReaction", errors.New(ctx.GetErrMsg())) | ||||||
| 		return | 		return | ||||||
| @@ -1486,20 +1519,22 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issue, err := models.GetIssueByID(comment.IssueID) | 	if err := comment.LoadIssue(); err != nil { | ||||||
| 	checkIssueRights(ctx, issue) | 		ctx.NotFoundOrServerError("LoadIssue", models.IsErrIssueNotExist, err) | ||||||
| 	if ctx.Written() { |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if ctx.HasError() { | 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | ||||||
| 		ctx.ServerError("ChangeCommentReaction", errors.New(ctx.GetErrMsg())) | 		ctx.Error(403) | ||||||
|  | 		return | ||||||
|  | 	} else if comment.Type != models.CommentTypeComment && comment.Type != models.CommentTypeCode { | ||||||
|  | 		ctx.Error(204) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch ctx.Params(":action") { | 	switch ctx.Params(":action") { | ||||||
| 	case "react": | 	case "react": | ||||||
| 		reaction, err := models.CreateCommentReaction(ctx.User, issue, comment, form.Content) | 		reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Content) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Info("CreateCommentReaction: %s", err) | 			log.Info("CreateCommentReaction: %s", err) | ||||||
| 			break | 			break | ||||||
| @@ -1511,9 +1546,9 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) { | |||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		log.Trace("Reaction for comment created: %d/%d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID, reaction.ID) | 		log.Trace("Reaction for comment created: %d/%d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID, reaction.ID) | ||||||
| 	case "unreact": | 	case "unreact": | ||||||
| 		if err := models.DeleteCommentReaction(ctx.User, issue, comment, form.Content); err != nil { | 		if err := models.DeleteCommentReaction(ctx.User, comment.Issue, comment, form.Content); err != nil { | ||||||
| 			ctx.ServerError("DeleteCommentReaction", err) | 			ctx.ServerError("DeleteCommentReaction", err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| @@ -1525,7 +1560,7 @@ func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) { | |||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		log.Trace("Reaction for comment removed: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID) | 		log.Trace("Reaction for comment removed: %d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID) | ||||||
| 	default: | 	default: | ||||||
| 		ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.Params(":action")), nil) | 		ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.Params(":action")), nil) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -14,23 +14,28 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // IssueWatch sets issue watching | // IssueWatch sets issue watching | ||||||
| func IssueWatch(c *context.Context) { | func IssueWatch(ctx *context.Context) { | ||||||
| 	watch, err := strconv.ParseBool(c.Req.PostForm.Get("watch")) | 	issue := GetActionIssue(ctx) | ||||||
|  | 	if ctx.Written() { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) { | ||||||
|  | 		ctx.Error(403) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch")) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.ServerError("watch is not bool", err) | 		ctx.ServerError("watch is not bool", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	issue := GetActionIssue(c) | 	if err := models.CreateOrUpdateIssueWatch(ctx.User.ID, issue.ID, watch); err != nil { | ||||||
| 	if c.Written() { | 		ctx.ServerError("CreateOrUpdateIssueWatch", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := models.CreateOrUpdateIssueWatch(c.User.ID, issue.ID, watch); err != nil { | 	url := fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index) | ||||||
| 		c.ServerError("CreateOrUpdateIssueWatch", err) | 	ctx.Redirect(url, http.StatusSeeOther) | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issue.Index) |  | ||||||
| 	c.Redirect(url, http.StatusSeeOther) |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -57,7 +57,13 @@ func getForkRepository(ctx *context.Context) *models.Repository { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !forkRepo.CanBeForked() || !forkRepo.HasAccess(ctx.User) { | 	perm, err := models.GetUserRepoPermission(forkRepo, ctx.User) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if forkRepo.IsBare || !perm.CanRead(models.UnitTypeCode) { | ||||||
| 		ctx.NotFound("getForkRepository", nil) | 		ctx.NotFound("getForkRepository", nil) | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| @@ -669,7 +675,12 @@ func ParseCompareInfo(ctx *context.Context) (*models.User, *models.Repository, * | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.User.IsWriterOfRepo(headRepo) && !ctx.User.IsAdmin { | 	perm, err := models.GetUserRepoPermission(headRepo, ctx.User) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 		return nil, nil, nil, nil, "", "" | ||||||
|  | 	} | ||||||
|  | 	if !perm.CanWrite(models.UnitTypeCode) { | ||||||
| 		log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID) | 		log.Trace("ParseCompareInfo[%d]: does not have write access or site admin", baseRepo.ID) | ||||||
| 		ctx.NotFound("ParseCompareInfo", nil) | 		ctx.NotFound("ParseCompareInfo", nil) | ||||||
| 		return nil, nil, nil, nil, "", "" | 		return nil, nil, nil, nil, "", "" | ||||||
| @@ -823,7 +834,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form) | 	labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, true) | ||||||
| 	if ctx.Written() { | 	if ctx.Written() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -969,7 +980,12 @@ func CleanUpPullRequest(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !ctx.User.IsWriterOfRepo(pr.HeadRepo) { | 	perm, err := models.GetUserRepoPermission(pr.HeadRepo, ctx.User) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("GetUserRepoPermission", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if !perm.CanWrite(models.UnitTypeCode) { | ||||||
| 		ctx.NotFound("CleanUpPullRequest", nil) | 		ctx.NotFound("CleanUpPullRequest", nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | // Copyright 2014 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -65,8 +66,11 @@ func Releases(ctx *context.Context) { | |||||||
| 		limit = 10 | 		limit = 10 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	writeAccess := ctx.Repo.CanWrite(models.UnitTypeReleases) | ||||||
|  | 	ctx.Data["CanCreateRelease"] = writeAccess | ||||||
|  |  | ||||||
| 	opts := models.FindReleasesOptions{ | 	opts := models.FindReleasesOptions{ | ||||||
| 		IncludeDrafts: ctx.Repo.IsWriter(), | 		IncludeDrafts: writeAccess, | ||||||
| 		IncludeTags:   true, | 		IncludeTags:   true, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -165,12 +165,21 @@ func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams []int64 | ||||||
| 		protectBranch.EnableWhitelist = f.EnableWhitelist | 		protectBranch.EnableWhitelist = f.EnableWhitelist | ||||||
| 		whitelistUsers, _ := base.StringsToInt64s(strings.Split(f.WhitelistUsers, ",")) | 		if strings.TrimSpace(f.WhitelistUsers) != "" { | ||||||
| 		whitelistTeams, _ := base.StringsToInt64s(strings.Split(f.WhitelistTeams, ",")) | 			whitelistUsers, _ = base.StringsToInt64s(strings.Split(f.WhitelistUsers, ",")) | ||||||
|  | 		} | ||||||
|  | 		if strings.TrimSpace(f.WhitelistTeams) != "" { | ||||||
|  | 			whitelistTeams, _ = base.StringsToInt64s(strings.Split(f.WhitelistTeams, ",")) | ||||||
|  | 		} | ||||||
| 		protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist | 		protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist | ||||||
| 		mergeWhitelistUsers, _ := base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ",")) | 		if strings.TrimSpace(f.MergeWhitelistUsers) != "" { | ||||||
| 		mergeWhitelistTeams, _ := base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) | 			mergeWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistUsers, ",")) | ||||||
|  | 		} | ||||||
|  | 		if strings.TrimSpace(f.MergeWhitelistTeams) != "" { | ||||||
|  | 			mergeWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.MergeWhitelistTeams, ",")) | ||||||
|  | 		} | ||||||
| 		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams) | 		err = models.UpdateProtectBranch(ctx.Repo.Repository, protectBranch, whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			ctx.ServerError("UpdateProtectBranch", err) | 			ctx.ServerError("UpdateProtectBranch", err) | ||||||
|   | |||||||
| @@ -137,7 +137,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { | |||||||
| 	ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses) | 	ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses) | ||||||
|  |  | ||||||
| 	// Check permission to add or upload new file. | 	// Check permission to add or upload new file. | ||||||
| 	if ctx.Repo.IsWriter() && ctx.Repo.IsViewBranch { | 	if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch { | ||||||
| 		ctx.Data["CanAddFile"] = true | 		ctx.Data["CanAddFile"] = true | ||||||
| 		ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled | 		ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled | ||||||
| 	} | 	} | ||||||
| @@ -256,7 +256,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st | |||||||
| 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") | 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") | ||||||
| 		} else if !ctx.Repo.IsViewBranch { | 		} else if !ctx.Repo.IsViewBranch { | ||||||
| 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") | 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") | ||||||
| 		} else if !ctx.Repo.IsWriter() { | 		} else if !ctx.Repo.CanWrite(models.UnitTypeCode) { | ||||||
| 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") | 			ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -275,16 +275,16 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st | |||||||
| 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file") | 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file") | ||||||
| 	} else if !ctx.Repo.IsViewBranch { | 	} else if !ctx.Repo.IsViewBranch { | ||||||
| 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") | 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") | ||||||
| 	} else if !ctx.Repo.IsWriter() { | 	} else if !ctx.Repo.CanWrite(models.UnitTypeCode) { | ||||||
| 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") | 		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Home render repository home page | // Home render repository home page | ||||||
| func Home(ctx *context.Context) { | func Home(ctx *context.Context) { | ||||||
| 	if len(ctx.Repo.Repository.Units) > 0 { | 	if len(ctx.Repo.Units) > 0 { | ||||||
| 		var firstUnit *models.Unit | 		var firstUnit *models.Unit | ||||||
| 		for _, repoUnit := range ctx.Repo.Repository.Units { | 		for _, repoUnit := range ctx.Repo.Units { | ||||||
| 			if repoUnit.Type == models.UnitTypeCode { | 			if repoUnit.Type == models.UnitTypeCode { | ||||||
| 				renderCode(ctx) | 				renderCode(ctx) | ||||||
| 				return | 				return | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Copyright 2015 The Gogs Authors. All rights reserved. | // Copyright 2015 The Gogs Authors. All rights reserved. | ||||||
|  | // Copyright 2018 The Gitea Authors. All rights reserved. | ||||||
| // Use of this source code is governed by a MIT-style | // Use of this source code is governed by a MIT-style | ||||||
| // license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
| @@ -30,8 +31,8 @@ const ( | |||||||
|  |  | ||||||
| // MustEnableWiki check if wiki is enabled, if external then redirect | // MustEnableWiki check if wiki is enabled, if external then redirect | ||||||
| func MustEnableWiki(ctx *context.Context) { | func MustEnableWiki(ctx *context.Context) { | ||||||
| 	if !ctx.Repo.Repository.UnitEnabled(models.UnitTypeWiki) && | 	if !ctx.Repo.CanRead(models.UnitTypeWiki) && | ||||||
| 		!ctx.Repo.Repository.UnitEnabled(models.UnitTypeExternalWiki) { | 		!ctx.Repo.CanRead(models.UnitTypeExternalWiki) { | ||||||
| 		ctx.NotFound("MustEnableWiki", nil) | 		ctx.NotFound("MustEnableWiki", nil) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -200,6 +201,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi | |||||||
| // Wiki renders single wiki page | // Wiki renders single wiki page | ||||||
| func Wiki(ctx *context.Context) { | func Wiki(ctx *context.Context) { | ||||||
| 	ctx.Data["PageIsWiki"] = true | 	ctx.Data["PageIsWiki"] = true | ||||||
|  | 	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) | ||||||
|  |  | ||||||
| 	if !ctx.Repo.Repository.HasWiki() { | 	if !ctx.Repo.Repository.HasWiki() { | ||||||
| 		ctx.Data["Title"] = ctx.Tr("repo.wiki") | 		ctx.Data["Title"] = ctx.Tr("repo.wiki") | ||||||
| @@ -235,14 +237,15 @@ func Wiki(ctx *context.Context) { | |||||||
|  |  | ||||||
| // WikiPages render wiki pages list page | // WikiPages render wiki pages list page | ||||||
| func WikiPages(ctx *context.Context) { | func WikiPages(ctx *context.Context) { | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.wiki.pages") |  | ||||||
| 	ctx.Data["PageIsWiki"] = true |  | ||||||
|  |  | ||||||
| 	if !ctx.Repo.Repository.HasWiki() { | 	if !ctx.Repo.Repository.HasWiki() { | ||||||
| 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki") | 		ctx.Redirect(ctx.Repo.RepoLink + "/wiki") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	ctx.Data["Title"] = ctx.Tr("repo.wiki.pages") | ||||||
|  | 	ctx.Data["PageIsWiki"] = true | ||||||
|  | 	ctx.Data["CanWriteWiki"] = ctx.Repo.CanWrite(models.UnitTypeWiki) | ||||||
|  |  | ||||||
| 	wikiRepo, commit, err := findWikiRepoCommit(ctx) | 	wikiRepo, commit, err := findWikiRepoCommit(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -393,7 +393,16 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	reqRepoAdmin := context.RequireRepoAdmin() | 	reqRepoAdmin := context.RequireRepoAdmin() | ||||||
| 	reqRepoWriter := context.RequireRepoWriter() | 	reqRepoCodeWriter := context.RequireRepoWriter(models.UnitTypeCode) | ||||||
|  | 	reqRepoCodeReader := context.RequireRepoReader(models.UnitTypeCode) | ||||||
|  | 	reqRepoReleaseWriter := context.RequireRepoWriter(models.UnitTypeReleases) | ||||||
|  | 	reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases) | ||||||
|  | 	reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki) | ||||||
|  | 	reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues) | ||||||
|  | 	reqRepoPullsWriter := context.RequireRepoWriter(models.UnitTypePullRequests) | ||||||
|  | 	reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests) | ||||||
|  | 	reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests) | ||||||
|  | 	reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests) | ||||||
|  |  | ||||||
| 	// ***** START: Organization ***** | 	// ***** START: Organization ***** | ||||||
| 	m.Group("/org", func() { | 	m.Group("/org", func() { | ||||||
| @@ -463,7 +472,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		m.Group("/fork", func() { | 		m.Group("/fork", func() { | ||||||
| 			m.Combo("/:repoid").Get(repo.Fork). | 			m.Combo("/:repoid").Get(repo.Fork). | ||||||
| 				Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) | 				Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) | ||||||
| 		}, context.RepoIDAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeCode)) | 		}, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) | ||||||
| 	}, reqSignIn) | 	}, reqSignIn) | ||||||
|  |  | ||||||
| 	m.Group("/:username/:reponame", func() { | 	m.Group("/:username/:reponame", func() { | ||||||
| @@ -514,7 +523,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		}, func(ctx *context.Context) { | 		}, func(ctx *context.Context) { | ||||||
| 			ctx.Data["PageIsSettings"] = true | 			ctx.Data["PageIsSettings"] = true | ||||||
| 		}) | 		}) | ||||||
| 	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.UnitTypes(), context.LoadRepoUnits(), context.RepoRef()) | 	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.UnitTypes(), context.RepoRef()) | ||||||
|  |  | ||||||
| 	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) | 	m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action) | ||||||
|  |  | ||||||
| @@ -522,7 +531,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		m.Group("/issues", func() { | 		m.Group("/issues", func() { | ||||||
| 			m.Combo("/new").Get(context.RepoRef(), repo.NewIssue). | 			m.Combo("/new").Get(context.RepoRef(), repo.NewIssue). | ||||||
| 				Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) | 				Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost) | ||||||
| 		}, context.CheckUnit(models.UnitTypeIssues)) | 		}, reqRepoIssueReader) | ||||||
| 		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. | 		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest. | ||||||
| 		// So they can apply their own enable/disable logic on routers. | 		// So they can apply their own enable/disable logic on routers. | ||||||
| 		m.Group("/issues", func() { | 		m.Group("/issues", func() { | ||||||
| @@ -545,22 +554,22 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 				m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction) | 				m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction) | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			m.Post("/labels", reqRepoWriter, repo.UpdateIssueLabel) | 			m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) | ||||||
| 			m.Post("/milestone", reqRepoWriter, repo.UpdateIssueMilestone) | 			m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) | ||||||
| 			m.Post("/assignee", reqRepoWriter, repo.UpdateIssueAssignee) | 			m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) | ||||||
| 			m.Post("/status", reqRepoWriter, repo.UpdateIssueStatus) | 			m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) | ||||||
| 		}) | 		}) | ||||||
| 		m.Group("/comments/:id", func() { | 		m.Group("/comments/:id", func() { | ||||||
| 			m.Post("", repo.UpdateCommentContent) | 			m.Post("", repo.UpdateCommentContent) | ||||||
| 			m.Post("/delete", repo.DeleteComment) | 			m.Post("/delete", repo.DeleteComment) | ||||||
| 			m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction) | 			m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction) | ||||||
| 		}, context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests)) | 		}) | ||||||
| 		m.Group("/labels", func() { | 		m.Group("/labels", func() { | ||||||
| 			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) | 			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) | ||||||
| 			m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) | 			m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) | ||||||
| 			m.Post("/delete", repo.DeleteLabel) | 			m.Post("/delete", repo.DeleteLabel) | ||||||
| 			m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels) | 			m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels) | ||||||
| 		}, reqRepoWriter, context.RepoRef(), context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests)) | 		}, reqRepoIssuesOrPullsWriter, context.RepoRef()) | ||||||
| 		m.Group("/milestones", func() { | 		m.Group("/milestones", func() { | ||||||
| 			m.Combo("/new").Get(repo.NewMilestone). | 			m.Combo("/new").Get(repo.NewMilestone). | ||||||
| 				Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) | 				Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) | ||||||
| @@ -568,9 +577,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) | 			m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) | ||||||
| 			m.Get("/:id/:action", repo.ChangeMilestonStatus) | 			m.Get("/:id/:action", repo.ChangeMilestonStatus) | ||||||
| 			m.Post("/delete", repo.DeleteMilestone) | 			m.Post("/delete", repo.DeleteMilestone) | ||||||
| 		}, reqRepoWriter, context.RepoRef(), context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests)) | 		}, reqRepoIssuesOrPullsWriter, context.RepoRef()) | ||||||
|  |  | ||||||
| 		m.Combo("/compare/*", repo.MustAllowPulls, repo.SetEditorconfigIfExists). | 		m.Combo("/compare/*", reqRepoCodeReader, reqRepoPullsReader, repo.MustAllowPulls, repo.SetEditorconfigIfExists). | ||||||
| 			Get(repo.SetDiffViewStyle, repo.CompareAndPullRequest). | 			Get(repo.SetDiffViewStyle, repo.CompareAndPullRequest). | ||||||
| 			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) | 			Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost) | ||||||
|  |  | ||||||
| @@ -591,7 +600,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 				m.Post("/upload-file", repo.UploadFileToServer) | 				m.Post("/upload-file", repo.UploadFileToServer) | ||||||
| 				m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) | 				m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) | ||||||
| 			}, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload) | 			}, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload) | ||||||
| 		}, repo.MustBeNotBare, reqRepoWriter) | 		}, reqRepoCodeWriter, repo.MustBeNotBare) | ||||||
|  |  | ||||||
| 		m.Group("/branches", func() { | 		m.Group("/branches", func() { | ||||||
| 			m.Group("/_new/", func() { | 			m.Group("/_new/", func() { | ||||||
| @@ -601,9 +610,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			}, bindIgnErr(auth.NewBranchForm{})) | 			}, bindIgnErr(auth.NewBranchForm{})) | ||||||
| 			m.Post("/delete", repo.DeleteBranchPost) | 			m.Post("/delete", repo.DeleteBranchPost) | ||||||
| 			m.Post("/restore", repo.RestoreBranchPost) | 			m.Post("/restore", repo.RestoreBranchPost) | ||||||
| 		}, reqRepoWriter, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode)) | 		}, reqRepoCodeWriter, repo.MustBeNotBare) | ||||||
|  |  | ||||||
| 	}, reqSignIn, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits()) | 	}, reqSignIn, context.RepoAssignment(), context.UnitTypes()) | ||||||
|  |  | ||||||
| 	// Releases | 	// Releases | ||||||
| 	m.Group("/:username/:reponame", func() { | 	m.Group("/:username/:reponame", func() { | ||||||
| @@ -614,11 +623,11 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			m.Get("/new", repo.NewRelease) | 			m.Get("/new", repo.NewRelease) | ||||||
| 			m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) | 			m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) | ||||||
| 			m.Post("/delete", repo.DeleteRelease) | 			m.Post("/delete", repo.DeleteRelease) | ||||||
| 		}, reqSignIn, repo.MustBeNotBare, reqRepoWriter, context.RepoRef()) | 		}, reqSignIn, repo.MustBeNotBare, reqRepoReleaseWriter, context.RepoRef()) | ||||||
| 		m.Group("/releases", func() { | 		m.Group("/releases", func() { | ||||||
| 			m.Get("/edit/*", repo.EditRelease) | 			m.Get("/edit/*", repo.EditRelease) | ||||||
| 			m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | 			m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | ||||||
| 		}, reqSignIn, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) { | 		}, reqSignIn, repo.MustBeNotBare, reqRepoReleaseWriter, func(ctx *context.Context) { | ||||||
| 			var err error | 			var err error | ||||||
| 			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) | 			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -632,7 +641,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			} | 			} | ||||||
| 			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount | 			ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount | ||||||
| 		}) | 		}) | ||||||
| 	}, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits(), context.CheckUnit(models.UnitTypeReleases)) | 	}, context.RepoAssignment(), context.UnitTypes(), reqRepoReleaseReader) | ||||||
|  |  | ||||||
| 	m.Group("/:username/:reponame", func() { | 	m.Group("/:username/:reponame", func() { | ||||||
| 		m.Post("/topics", repo.TopicsPost) | 		m.Post("/topics", repo.TopicsPost) | ||||||
| @@ -642,8 +651,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		m.Group("", func() { | 		m.Group("", func() { | ||||||
| 			m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues) | 			m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues) | ||||||
| 			m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue) | 			m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue) | ||||||
| 			m.Get("/labels/", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.RetrieveLabels, repo.Labels) | 			m.Get("/labels/", reqRepoIssuesOrPullsReader, repo.RetrieveLabels, repo.Labels) | ||||||
| 			m.Get("/milestones", context.CheckAnyUnit(models.UnitTypeIssues, models.UnitTypePullRequests), repo.Milestones) | 			m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones) | ||||||
| 		}, context.RepoRef()) | 		}, context.RepoRef()) | ||||||
|  |  | ||||||
| 		m.Group("/wiki", func() { | 		m.Group("/wiki", func() { | ||||||
| @@ -656,7 +665,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 				m.Combo("/:page/_edit").Get(repo.EditWiki). | 				m.Combo("/:page/_edit").Get(repo.EditWiki). | ||||||
| 					Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost) | 					Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost) | ||||||
| 				m.Post("/:page/delete", repo.DeleteWikiPagePost) | 				m.Post("/:page/delete", repo.DeleteWikiPagePost) | ||||||
| 			}, reqSignIn, reqRepoWriter) | 			}, reqSignIn, reqRepoWikiWriter) | ||||||
| 		}, repo.MustEnableWiki, context.RepoRef()) | 		}, repo.MustEnableWiki, context.RepoRef()) | ||||||
|  |  | ||||||
| 		m.Group("/wiki", func() { | 		m.Group("/wiki", func() { | ||||||
| @@ -666,19 +675,19 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 		m.Group("/activity", func() { | 		m.Group("/activity", func() { | ||||||
| 			m.Get("", repo.Activity) | 			m.Get("", repo.Activity) | ||||||
| 			m.Get("/:period", repo.Activity) | 			m.Get("/:period", repo.Activity) | ||||||
| 		}, context.RepoRef(), repo.MustBeNotBare, context.CheckAnyUnit(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases)) | 		}, context.RepoRef(), repo.MustBeNotBare, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases)) | ||||||
|  |  | ||||||
| 		m.Get("/archive/*", repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.Download) | 		m.Get("/archive/*", repo.MustBeNotBare, reqRepoCodeReader, repo.Download) | ||||||
|  |  | ||||||
| 		m.Group("/branches", func() { | 		m.Group("/branches", func() { | ||||||
| 			m.Get("", repo.Branches) | 			m.Get("", repo.Branches) | ||||||
| 		}, repo.MustBeNotBare, context.RepoRef(), context.CheckUnit(models.UnitTypeCode)) | 		}, repo.MustBeNotBare, context.RepoRef(), reqRepoCodeReader) | ||||||
|  |  | ||||||
| 		m.Group("/pulls/:index", func() { | 		m.Group("/pulls/:index", func() { | ||||||
| 			m.Get(".diff", repo.DownloadPullDiff) | 			m.Get(".diff", repo.DownloadPullDiff) | ||||||
| 			m.Get(".patch", repo.DownloadPullPatch) | 			m.Get(".patch", repo.DownloadPullPatch) | ||||||
| 			m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) | 			m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) | ||||||
| 			m.Post("/merge", reqRepoWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest) | 			m.Post("/merge", reqRepoPullsWriter, bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest) | ||||||
| 			m.Post("/cleanup", context.RepoRef(), repo.CleanUpPullRequest) | 			m.Post("/cleanup", context.RepoRef(), repo.CleanUpPullRequest) | ||||||
| 			m.Group("/files", func() { | 			m.Group("/files", func() { | ||||||
| 				m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) | 				m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) | ||||||
| @@ -696,7 +705,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			m.Get("/blob/:sha", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID) | 			m.Get("/blob/:sha", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID) | ||||||
| 			// "/*" route is deprecated, and kept for backward compatibility | 			// "/*" route is deprecated, and kept for backward compatibility | ||||||
| 			m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload) | 			m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload) | ||||||
| 		}, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode)) | 		}, repo.MustBeNotBare, reqRepoCodeReader) | ||||||
|  |  | ||||||
| 		m.Group("/commits", func() { | 		m.Group("/commits", func() { | ||||||
| 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits) | 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits) | ||||||
| @@ -704,12 +713,12 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
| 			m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits) | 			m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits) | ||||||
| 			// "/*" route is deprecated, and kept for backward compatibility | 			// "/*" route is deprecated, and kept for backward compatibility | ||||||
| 			m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits) | 			m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits) | ||||||
| 		}, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode)) | 		}, repo.MustBeNotBare, reqRepoCodeReader) | ||||||
|  |  | ||||||
| 		m.Group("", func() { | 		m.Group("", func() { | ||||||
| 			m.Get("/graph", repo.Graph) | 			m.Get("/graph", repo.Graph) | ||||||
| 			m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff) | 			m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff) | ||||||
| 		}, repo.MustBeNotBare, context.RepoRef(), context.CheckUnit(models.UnitTypeCode)) | 		}, repo.MustBeNotBare, context.RepoRef(), reqRepoCodeReader) | ||||||
|  |  | ||||||
| 		m.Group("/src", func() { | 		m.Group("/src", func() { | ||||||
| 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) | 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) | ||||||
| @@ -721,24 +730,24 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||||
|  |  | ||||||
| 		m.Group("", func() { | 		m.Group("", func() { | ||||||
| 			m.Get("/forks", repo.Forks) | 			m.Get("/forks", repo.Forks) | ||||||
| 		}, context.RepoRef(), context.CheckUnit(models.UnitTypeCode)) | 		}, context.RepoRef(), reqRepoCodeReader) | ||||||
| 		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", | 		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", | ||||||
| 			repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.RawDiff) | 			repo.MustBeNotBare, reqRepoCodeReader, repo.RawDiff) | ||||||
|  |  | ||||||
| 		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, | 		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, | ||||||
| 			repo.SetDiffViewStyle, repo.MustBeNotBare, context.CheckUnit(models.UnitTypeCode), repo.CompareDiff) | 			repo.SetDiffViewStyle, repo.MustBeNotBare, reqRepoCodeReader, repo.CompareDiff) | ||||||
| 	}, ignSignIn, context.RepoAssignment(), context.UnitTypes(), context.LoadRepoUnits()) | 	}, ignSignIn, context.RepoAssignment(), context.UnitTypes()) | ||||||
| 	m.Group("/:username/:reponame", func() { | 	m.Group("/:username/:reponame", func() { | ||||||
| 		m.Get("/stars", repo.Stars) | 		m.Get("/stars", repo.Stars) | ||||||
| 		m.Get("/watchers", repo.Watchers) | 		m.Get("/watchers", repo.Watchers) | ||||||
| 		m.Get("/search", context.CheckUnit(models.UnitTypeCode), repo.Search) | 		m.Get("/search", reqRepoCodeReader, repo.Search) | ||||||
| 	}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes(), context.LoadRepoUnits()) | 	}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) | ||||||
|  |  | ||||||
| 	m.Group("/:username", func() { | 	m.Group("/:username", func() { | ||||||
| 		m.Group("/:reponame", func() { | 		m.Group("/:reponame", func() { | ||||||
| 			m.Get("", repo.SetEditorconfigIfExists, repo.Home) | 			m.Get("", repo.SetEditorconfigIfExists, repo.Home) | ||||||
| 			m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home) | 			m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home) | ||||||
| 		}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes(), context.LoadRepoUnits()) | 		}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes()) | ||||||
|  |  | ||||||
| 		m.Group("/:reponame", func() { | 		m.Group("/:reponame", func() { | ||||||
| 			m.Group("\\.git/info/lfs", func() { | 			m.Group("\\.git/info/lfs", func() { | ||||||
|   | |||||||
| @@ -286,7 +286,12 @@ func Issues(ctx *context.Context) { | |||||||
| 		repo := showReposMap[repoID] | 		repo := showReposMap[repoID] | ||||||
|  |  | ||||||
| 		// Check if user has access to given repository. | 		// Check if user has access to given repository. | ||||||
| 		if !repo.IsOwnedBy(ctxUser.ID) && !repo.HasAccess(ctxUser) { | 		perm, err := models.GetUserRepoPermission(repo, ctxUser) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("GetUserRepoPermission", fmt.Errorf("[%d]%v", repoID, err)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if !perm.CanRead(models.UnitTypeIssues) { | ||||||
| 			ctx.Status(404) | 			ctx.Status(404) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -23,10 +23,10 @@ | |||||||
| 		</h2> | 		</h2> | ||||||
| 		<div class="ui divider"></div> | 		<div class="ui divider"></div> | ||||||
|  |  | ||||||
| 		{{if (or (.Repository.UnitEnabled $.UnitTypeIssues) (.Repository.UnitEnabled $.UnitTypePullRequests))}} | 		{{if (or (.Permission.CanRead $.UnitTypeIssues) (.Permission.CanRead $.UnitTypePullRequests))}} | ||||||
| 		<h4 class="ui top attached header">{{.i18n.Tr "repo.activity.overview"}}</h4> | 		<h4 class="ui top attached header">{{.i18n.Tr "repo.activity.overview"}}</h4> | ||||||
| 		<div class="ui attached segment two column grid"> | 		<div class="ui attached segment two column grid"> | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypePullRequests}} | 			{{if .Permission.CanRead $.UnitTypePullRequests}} | ||||||
| 				<div class="column"> | 				<div class="column"> | ||||||
| 					{{if gt .Activity.ActivePRCount 0}} | 					{{if gt .Activity.ActivePRCount 0}} | ||||||
| 					<div class="stats-table"> | 					<div class="stats-table"> | ||||||
| @@ -41,7 +41,7 @@ | |||||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n") .Activity.ActivePRCount | Safe }} | 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ActivePRCount "repo.activity.active_prs_count_1" "repo.activity.active_prs_count_n") .Activity.ActivePRCount | Safe }} | ||||||
| 				</div> | 				</div> | ||||||
| 			{{end}} | 			{{end}} | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeIssues}} | 			{{if .Permission.CanRead $.UnitTypeIssues}} | ||||||
| 				<div class="column"> | 				<div class="column"> | ||||||
| 					{{if gt .Activity.ActiveIssueCount 0}} | 					{{if gt .Activity.ActiveIssueCount 0}} | ||||||
| 					<div class="stats-table"> | 					<div class="stats-table"> | ||||||
| @@ -58,7 +58,7 @@ | |||||||
| 			{{end}} | 			{{end}} | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="ui attached segment horizontal segments"> | 		<div class="ui attached segment horizontal segments"> | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypePullRequests}} | 			{{if .Permission.CanRead $.UnitTypePullRequests}} | ||||||
| 				<a href="#merged-pull-requests" class="ui attached segment text center"> | 				<a href="#merged-pull-requests" class="ui attached segment text center"> | ||||||
| 					<i class="text purple octicon octicon-git-pull-request"></i> <strong>{{.Activity.MergedPRCount}}</strong><br> | 					<i class="text purple octicon octicon-git-pull-request"></i> <strong>{{.Activity.MergedPRCount}}</strong><br> | ||||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n") }} | 					{{.i18n.Tr (TrN .i18n.Lang .Activity.MergedPRCount "repo.activity.merged_prs_count_1" "repo.activity.merged_prs_count_n") }} | ||||||
| @@ -68,7 +68,7 @@ | |||||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n") }} | 					{{.i18n.Tr (TrN .i18n.Lang .Activity.OpenedPRCount "repo.activity.opened_prs_count_1" "repo.activity.opened_prs_count_n") }} | ||||||
| 				</a> | 				</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeIssues}} | 			{{if .Permission.CanRead $.UnitTypeIssues}} | ||||||
| 				<a href="#closed-issues" class="ui attached segment text center"> | 				<a href="#closed-issues" class="ui attached segment text center"> | ||||||
| 					<i class="text red octicon octicon-issue-closed"></i> <strong>{{.Activity.ClosedIssueCount}}</strong><br> | 					<i class="text red octicon octicon-issue-closed"></i> <strong>{{.Activity.ClosedIssueCount}}</strong><br> | ||||||
| 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n") }} | 					{{.i18n.Tr (TrN .i18n.Lang .Activity.ClosedIssueCount "repo.activity.closed_issues_count_1" "repo.activity.closed_issues_count_n") }} | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| 		<div class="ui grid"> | 		<div class="ui grid"> | ||||||
| 			<div class="sixteen wide column content"> | 			<div class="sixteen wide column content"> | ||||||
| 				{{template "base/alert" .}} | 				{{template "base/alert" .}} | ||||||
| 				{{if .IsRepositoryWriter}} | 				{{if .CanWriteCode}} | ||||||
| 					<h4 class="ui top attached header"> | 					<h4 class="ui top attached header"> | ||||||
| 						{{.i18n.Tr "repo.quick_guide"}} | 						{{.i18n.Tr "repo.quick_guide"}} | ||||||
| 					</h4> | 					</h4> | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ | |||||||
| 				{{end}} | 				{{end}} | ||||||
| 			{{end}} | 			{{end}} | ||||||
| 			{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) }} | 			{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) }} | ||||||
| 			{{if or $.root.IsRepositoryAdmin (eq .Poster.ID $.root.SignedUserID)}} | 			{{if or $.root.Permission.IsAdmin (eq .Poster.ID $.root.SignedUserID)}} | ||||||
| 				<div class="item action"> | 				<div class="item action"> | ||||||
| 					<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | 					<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | ||||||
| 					<a class="delete-comment" href="#" data-comment-id={{.HashTag}} data-url="{{$.root.RepoLink}}/comments/{{.ID}}/delete" data-locale="{{$.root.i18n.Tr "repo.issues.delete_comment_confirm"}}"><i class="octicon octicon-x"></i></a> | 					<a class="delete-comment" href="#" data-comment-id={{.HashTag}} data-url="{{$.root.RepoLink}}/comments/{{.ID}}/delete" data-locale="{{$.root.i18n.Tr "repo.issues.delete_comment_confirm"}}"><i class="octicon octicon-x"></i></a> | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ | |||||||
| 						{{.NumStars}} | 						{{.NumStars}} | ||||||
| 					</a> | 					</a> | ||||||
| 				</div> | 				</div> | ||||||
| 				{{if .CanBeForked}} | 				{{if and (not .IsBare) ($.Permission.CanRead $.UnitTypeCode)}} | ||||||
| 					<div class="ui compact labeled button" tabindex="0"> | 					<div class="ui compact labeled button" tabindex="0"> | ||||||
| 						<a class="ui compact button {{if not $.CanSignedUserFork}}poping up{{end}}" {{if $.CanSignedUserFork}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else if $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" data-position="top center" data-variation="tiny"{{end}}> | 						<a class="ui compact button {{if not $.CanSignedUserFork}}poping up{{end}}" {{if $.CanSignedUserFork}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else if $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" data-position="top center" data-variation="tiny"{{end}}> | ||||||
| 							<i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} | 							<i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} | ||||||
| @@ -47,43 +47,43 @@ | |||||||
| {{if not .IsDiffCompare}} | {{if not .IsDiffCompare}} | ||||||
| 	<div class="ui tabs container"> | 	<div class="ui tabs container"> | ||||||
| 		<div class="ui tabular stackable menu navbar"> | 		<div class="ui tabular stackable menu navbar"> | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeCode}} | 			{{if .Permission.CanRead $.UnitTypeCode}} | ||||||
| 			<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}"> | 			<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}"> | ||||||
| 				<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} | 				<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} | ||||||
| 			</a> | 			</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeIssues}} | 			{{if .Permission.CanRead $.UnitTypeIssues}} | ||||||
| 				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | 				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | ||||||
| 					<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span> | 					<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span> | ||||||
| 				</a> | 				</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeExternalTracker}} | 			{{if .Permission.CanRead $.UnitTypeExternalTracker}} | ||||||
| 				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues" target="_blank" rel="noopener noreferrer"> | 				<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues" target="_blank" rel="noopener noreferrer"> | ||||||
| 					<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} </span> | 					<i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} </span> | ||||||
| 				</a> | 				</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if .Repository.AllowsPulls}} | 			{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}} | ||||||
| 				<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls"> | 				<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls"> | ||||||
| 					<i class="octicon octicon-git-pull-request"></i> {{.i18n.Tr "repo.pulls"}} <span class="ui {{if not .Repository.NumOpenPulls}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenPulls}}</span> | 					<i class="octicon octicon-git-pull-request"></i> {{.i18n.Tr "repo.pulls"}} <span class="ui {{if not .Repository.NumOpenPulls}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenPulls}}</span> | ||||||
| 				</a> | 				</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if and (.Repository.UnitEnabled $.UnitTypeReleases) (not .IsBareRepo) }} | 			{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsBareRepo) }} | ||||||
| 			<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases"> | 			<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases"> | ||||||
| 				<i class="octicon octicon-tag"></i> {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .Repository.NumReleases}}gray{{else}}blue{{end}} small label">{{.Repository.NumReleases}}</span> | 				<i class="octicon octicon-tag"></i> {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .Repository.NumReleases}}gray{{else}}blue{{end}} small label">{{.Repository.NumReleases}}</span> | ||||||
| 			</a> | 			</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if or (.Repository.UnitEnabled $.UnitTypeWiki) (.Repository.UnitEnabled $.UnitTypeExternalWiki)}} | 			{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}} | ||||||
| 				<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Repository.UnitEnabled $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}> | 				<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}> | ||||||
| 					<i class="octicon octicon-book"></i> {{.i18n.Tr "repo.wiki"}} | 					<i class="octicon octicon-book"></i> {{.i18n.Tr "repo.wiki"}} | ||||||
| 				</a> | 				</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if and (.Repository.AnyUnitEnabled $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsBareRepo)}} | 			{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsBareRepo)}} | ||||||
| 				<a class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | 				<a class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity"> | ||||||
| 					<i class="octicon octicon-pulse"></i> {{.i18n.Tr "repo.activity"}} | 					<i class="octicon octicon-pulse"></i> {{.i18n.Tr "repo.activity"}} | ||||||
| 				</a> | 				</a> | ||||||
| @@ -91,7 +91,7 @@ | |||||||
|  |  | ||||||
| 			{{template "custom/extra_tabs" .}} | 			{{template "custom/extra_tabs" .}} | ||||||
|  |  | ||||||
| 			{{if .IsRepositoryAdmin}} | 			{{if .Permission.IsAdmin}} | ||||||
| 				<div class="right menu"> | 				<div class="right menu"> | ||||||
| 					<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings"> | 					<a class="{{if .PageIsSettings}}active{{end}} item" href="{{.RepoLink}}/settings"> | ||||||
| 						<i class="octicon octicon-tools"></i> {{.i18n.Tr "repo.settings"}} | 						<i class="octicon octicon-tools"></i> {{.i18n.Tr "repo.settings"}} | ||||||
|   | |||||||
| @@ -25,9 +25,9 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		<div class="ui repo-topic" id="repo-topic"> | 		<div class="ui repo-topic" id="repo-topic"> | ||||||
| 		{{range .Topics}}<a class="ui green basic label topic" style="cursor:pointer;" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}} | 		{{range .Topics}}<a class="ui green basic label topic" style="cursor:pointer;" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}} | ||||||
| 		{{if .IsRepositoryAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}} | 		{{if .Permission.IsAdmin}}<a id="manage_topic" style="cursor:pointer;margin-left:10px;">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}} | ||||||
| 		</div> | 		</div> | ||||||
| 		{{if .IsRepositoryAdmin}} | 		{{if .Permission.IsAdmin}} | ||||||
| 		<div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none"> | 		<div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none"> | ||||||
| 			<div class="fourteen wide column"> | 			<div class="fourteen wide column"> | ||||||
| 				<div class="field"> | 				<div class="field"> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 	<div class="ui container"> | 	<div class="ui container"> | ||||||
| 		<div class="navbar"> | 		<div class="navbar"> | ||||||
| 			{{template "repo/issue/navbar" .}} | 			{{template "repo/issue/navbar" .}} | ||||||
| 			{{if .IsRepositoryWriter}} | 			{{if or .CanWriteIssues .CanWritePulls}} | ||||||
| 				<div class="ui right"> | 				<div class="ui right"> | ||||||
| 					<div class="ui green new-label button">{{.i18n.Tr "repo.issues.new_label"}}</div> | 					<div class="ui green new-label button">{{.i18n.Tr "repo.issues.new_label"}}</div> | ||||||
| 				</div> | 				</div> | ||||||
| @@ -57,7 +57,7 @@ | |||||||
| 		{{template "base/alert" .}} | 		{{template "base/alert" .}} | ||||||
| 		<div class="ui black label">{{.i18n.Tr "repo.issues.label_count" .NumLabels}}</div> | 		<div class="ui black label">{{.i18n.Tr "repo.issues.label_count" .NumLabels}}</div> | ||||||
| 		<div class="label list"> | 		<div class="label list"> | ||||||
| 			{{if and $.IsRepositoryWriter (eq .NumLabels 0)}} | 			{{if and (or $.CanWriteIssues $.CanWritePulls) (eq .NumLabels 0)}} | ||||||
| 				<div class="ui centered grid"> | 				<div class="ui centered grid"> | ||||||
| 					<div class="twelve wide column eight wide computer column"> | 					<div class="twelve wide column eight wide computer column"> | ||||||
| 						<div class="ui attached left aligned segment"> | 						<div class="ui attached left aligned segment"> | ||||||
| @@ -105,7 +105,7 @@ | |||||||
| 							<a class="ui right open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a> | 							<a class="ui right open-issues" href="{{$.RepoLink}}/issues?labels={{.ID}}"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.label_open_issues" .NumOpenIssues}}</a> | ||||||
| 						</div> | 						</div> | ||||||
| 						<div class="three wide column"> | 						<div class="three wide column"> | ||||||
| 						{{if $.IsRepositoryWriter}} | 						{{if or $.CanWriteIssues $.CanWritePulls}} | ||||||
| 							<a class="ui right delete-button" href="#" data-url="{{$.RepoLink}}/labels/delete" data-id="{{.ID}}"><i class="octicon octicon-trashcan"></i> {{$.i18n.Tr "repo.issues.label_delete"}}</a> | 							<a class="ui right delete-button" href="#" data-url="{{$.RepoLink}}/labels/delete" data-id="{{.ID}}"><i class="octicon octicon-trashcan"></i> {{$.i18n.Tr "repo.issues.label_delete"}}</a> | ||||||
| 							<a class="ui right edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" data-description="{{.Description}}" data-color={{.Color}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | 							<a class="ui right edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" data-description="{{.Description}}" data-color={{.Color}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | ||||||
| 						{{end}} | 						{{end}} | ||||||
| @@ -117,7 +117,7 @@ | |||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| {{if .IsRepositoryWriter}} | {{if or $.CanWriteIssues $.CanWritePulls}} | ||||||
| 	<div class="ui small basic delete modal"> | 	<div class="ui small basic delete modal"> | ||||||
| 		<div class="ui icon header"> | 		<div class="ui icon header"> | ||||||
| 			<i class="trash icon"></i> | 			<i class="trash icon"></i> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 	<div class="ui container"> | 	<div class="ui container"> | ||||||
| 		<div class="navbar"> | 		<div class="navbar"> | ||||||
| 			{{template "repo/issue/navbar" .}} | 			{{template "repo/issue/navbar" .}} | ||||||
| 			{{if and .IsRepositoryWriter .PageIsEditMilestone}} | 			{{if and (or .CanWriteIssues .CanWritePulls) .PageIsEditMilestone}} | ||||||
| 				<div class="ui right floated secondary menu"> | 				<div class="ui right floated secondary menu"> | ||||||
| 					<a class="ui green button" href="{{$.RepoLink}}/milestones/new">{{.i18n.Tr "repo.milestones.new"}}</a> | 					<a class="ui green button" href="{{$.RepoLink}}/milestones/new">{{.i18n.Tr "repo.milestones.new"}}</a> | ||||||
| 				</div> | 				</div> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 	<div class="ui container"> | 	<div class="ui container"> | ||||||
| 		<div class="navbar"> | 		<div class="navbar"> | ||||||
| 			{{template "repo/issue/navbar" .}} | 			{{template "repo/issue/navbar" .}} | ||||||
| 			{{if .IsRepositoryWriter}} | 			{{if or .CanWriteIssues .CanWritePulls}} | ||||||
| 				<div class="ui right"> | 				<div class="ui right"> | ||||||
| 					<a class="ui green button" href="{{$.Link}}/new">{{.i18n.Tr "repo.milestones.new"}}</a> | 					<a class="ui green button" href="{{$.Link}}/new">{{.i18n.Tr "repo.milestones.new"}}</a> | ||||||
| 				</div> | 				</div> | ||||||
| @@ -67,7 +67,7 @@ | |||||||
| 							{{if .TotalTrackedTime}}<i class="octicon octicon-clock"></i> {{.TotalTrackedTime|Sec2Time}}{{end}} | 							{{if .TotalTrackedTime}}<i class="octicon octicon-clock"></i> {{.TotalTrackedTime|Sec2Time}}{{end}} | ||||||
| 						</span> | 						</span> | ||||||
| 					</div> | 					</div> | ||||||
| 					{{if $.IsRepositoryWriter}} | 					{{if or $.CanWriteIssues $.CanWritePulls}} | ||||||
| 						<div class="ui right operate"> | 						<div class="ui right operate"> | ||||||
| 							<a href="{{$.Link}}/{{.ID}}/edit" data-id={{.ID}} data-title={{.Name}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | 							<a href="{{$.Link}}/{{.ID}}/edit" data-id={{.ID}} data-title={{.Name}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a> | ||||||
| 							{{if .IsClosed}} | 							{{if .IsClosed}} | ||||||
| @@ -111,7 +111,7 @@ | |||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| {{if .IsRepositoryWriter}} | {{if or .CanWriteIssues .CanWritePulls}} | ||||||
| 	<div class="ui small basic delete modal"> | 	<div class="ui small basic delete modal"> | ||||||
| 		<div class="ui icon header"> | 		<div class="ui icon header"> | ||||||
| 			<i class="trash icon"></i> | 			<i class="trash icon"></i> | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ | |||||||
| 						<span class="text grey"><a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}}</span> | 						<span class="text grey"><a {{if gt .Issue.Poster.ID 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}}</span> | ||||||
| 						<div class="ui right actions"> | 						<div class="ui right actions"> | ||||||
| 							{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }} | 							{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) }} | ||||||
| 							{{if .IsIssueOwner}} | 							{{if or .IsIssueWriter .IsIssuePoster}} | ||||||
| 								<div class="item action"> | 								<div class="item action"> | ||||||
| 									<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | 									<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | ||||||
| 								</div> | 								</div> | ||||||
| @@ -79,7 +79,7 @@ | |||||||
| 							{{.CsrfTokenHtml}} | 							{{.CsrfTokenHtml}} | ||||||
| 							<input id="status" name="status" type="hidden"> | 							<input id="status" name="status" type="hidden"> | ||||||
| 							<div class="text right"> | 							<div class="text right"> | ||||||
| 								{{if and .IsIssueOwner (not .DisableStatusChange)}} | 								{{if and (or .IsIssueWriter .IsIssuePoster) (not .DisableStatusChange)}} | ||||||
| 									{{if .Issue.IsClosed}} | 									{{if .Issue.IsClosed}} | ||||||
| 										<div id="status-button" class="ui green basic button" tabindex="6" data-status="{{.i18n.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.reopen_comment_issue"}}" data-status-val="reopen"> | 										<div id="status-button" class="ui green basic button" tabindex="6" data-status="{{.i18n.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{.i18n.Tr "repo.issues.reopen_comment_issue"}}" data-status-val="reopen"> | ||||||
| 											{{.i18n.Tr "repo.issues.reopen_issue"}} | 											{{.i18n.Tr "repo.issues.reopen_issue"}} | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ | |||||||
| 							</div> | 							</div> | ||||||
| 						{{end}} | 						{{end}} | ||||||
| 						{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) }} | 						{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) }} | ||||||
| 						{{if or $.IsRepositoryAdmin (eq .Poster.ID $.SignedUserID)}} | 						{{if or $.Permission.IsAdmin (eq .Poster.ID $.SignedUserID)}} | ||||||
| 							<div class="item action"> | 							<div class="item action"> | ||||||
| 								<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | 								<a class="edit-content" href="#"><i class="octicon octicon-pencil"></i></a> | ||||||
| 								<a class="delete-comment" href="#" data-comment-id={{.HashTag}} data-url="{{$.RepoLink}}/comments/{{.ID}}/delete" data-locale="{{$.i18n.Tr "repo.issues.delete_comment_confirm"}}"><i class="octicon octicon-x"></i></a> | 								<a class="delete-comment" href="#" data-comment-id={{.HashTag}} data-url="{{$.RepoLink}}/comments/{{.ID}}/delete" data-locale="{{$.i18n.Tr "repo.issues.delete_comment_confirm"}}"><i class="octicon octicon-x"></i></a> | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| 	<div class="ui segment metas"> | 	<div class="ui segment metas"> | ||||||
| 		{{template "repo/issue/branch_selector_field" .}} | 		{{template "repo/issue/branch_selector_field" .}} | ||||||
|  |  | ||||||
| 		<div class="ui {{if not .IsRepositoryWriter}}disabled{{end}} floating jump select-label dropdown"> | 		<div class="ui {{if not .IsIssueWriter}}disabled{{end}} floating jump select-label dropdown"> | ||||||
| 			<span class="text"> | 			<span class="text"> | ||||||
| 				<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> | 				<strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> | ||||||
| 				<span class="octicon octicon-gear"></span> | 				<span class="octicon octicon-gear"></span> | ||||||
| @@ -27,7 +27,7 @@ | |||||||
|  |  | ||||||
| 		<div class="ui divider"></div> | 		<div class="ui divider"></div> | ||||||
|  |  | ||||||
| 		<div class="ui {{if not .IsRepositoryWriter}}disabled{{end}} floating jump select-milestone dropdown"> | 		<div class="ui {{if not .IsIssueWriter}}disabled{{end}} floating jump select-milestone dropdown"> | ||||||
| 			<span class="text"> | 			<span class="text"> | ||||||
| 				<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong> | 				<strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong> | ||||||
| 				<span class="octicon octicon-gear"></span> | 				<span class="octicon octicon-gear"></span> | ||||||
| @@ -68,7 +68,7 @@ | |||||||
| 		<div class="ui divider"></div> | 		<div class="ui divider"></div> | ||||||
|  |  | ||||||
| 		<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}"> | 		<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}"> | ||||||
| 		<div class="ui {{if not .IsRepositoryWriter}}disabled{{end}} floating jump select-assignees-modify dropdown"> | 		<div class="ui {{if not .IsIssueWriter}}disabled{{end}} floating jump select-assignees-modify dropdown"> | ||||||
| 			<span class="text"> | 			<span class="text"> | ||||||
| 				<strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong> | 				<strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong> | ||||||
| 				<span class="octicon octicon-gear"></span> | 				<span class="octicon octicon-gear"></span> | ||||||
| @@ -223,7 +223,7 @@ | |||||||
| 					{{if .Issue.IsOverdue}} | 					{{if .Issue.IsOverdue}} | ||||||
| 						<span style="color: red;">{{.i18n.Tr "repo.issues.due_date_overdue"}}</span> | 						<span style="color: red;">{{.i18n.Tr "repo.issues.due_date_overdue"}}</span> | ||||||
| 					{{end}} | 					{{end}} | ||||||
| 					{{if and .IsSigned .IsRepositoryWriter}} | 					{{if .IsIssueWriter}} | ||||||
| 						<br/> | 						<br/> | ||||||
| 						<a style="cursor:pointer;" onclick="toggleDeadlineForm();"><i class="edit icon"></i>{{$.i18n.Tr "repo.issues.due_date_form_edit"}}</a> - | 						<a style="cursor:pointer;" onclick="toggleDeadlineForm();"><i class="edit icon"></i>{{$.i18n.Tr "repo.issues.due_date_form_edit"}}</a> - | ||||||
| 						<a style="cursor:pointer;" onclick="updateDeadline('');"><i class="remove icon"></i>{{$.i18n.Tr "repo.issues.due_date_form_remove"}}</a> | 						<a style="cursor:pointer;" onclick="updateDeadline('');"><i class="remove icon"></i>{{$.i18n.Tr "repo.issues.due_date_form_remove"}}</a> | ||||||
| @@ -233,7 +233,7 @@ | |||||||
| 				<p><i>{{.i18n.Tr "repo.issues.due_date_not_set"}}</i></p> | 				<p><i>{{.i18n.Tr "repo.issues.due_date_not_set"}}</i></p> | ||||||
| 			{{end}} | 			{{end}} | ||||||
|  |  | ||||||
| 			{{if and .IsSigned .IsRepositoryWriter}} | 			{{if .IsIssueWriter}} | ||||||
| 				<div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm"> | 				<div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm"> | ||||||
| 					<form class="ui fluid action input" action="{{AppSubUrl}}/api/v1/repos/{{.Repository.Owner.Name}}/{{.Repository.Name}}/issues/{{.Issue.Index}}" method="post" id="update-issue-deadline-form" onsubmit="setDeadline();return false;"> | 					<form class="ui fluid action input" action="{{AppSubUrl}}/api/v1/repos/{{.Repository.Owner.Name}}/{{.Repository.Name}}/issues/{{.Issue.Index}}" method="post" id="update-issue-deadline-form" onsubmit="setDeadline();return false;"> | ||||||
| 						{{$.CsrfTokenHtml}} | 						{{$.CsrfTokenHtml}} | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| 				<input value="{{.Issue.Title}}"> | 				<input value="{{.Issue.Title}}"> | ||||||
| 			</div> | 			</div> | ||||||
| 		</h1> | 		</h1> | ||||||
| 		{{if .IsIssueOwner}} | 		{{if or .IsIssueWriter .IsIssuePoster}} | ||||||
| 			<div class="four wide column"> | 			<div class="four wide column"> | ||||||
| 				<div class="edit-zone text right"> | 				<div class="edit-zone text right"> | ||||||
| 					<div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div> | 					<div id="edit-title" class="ui basic green not-in-edit button">{{.i18n.Tr "repo.issues.edit"}}</div> | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| 		{{template "base/alert" .}} | 		{{template "base/alert" .}} | ||||||
| 		<h2 class="ui header"> | 		<h2 class="ui header"> | ||||||
| 			{{.i18n.Tr "repo.release.releases"}} | 			{{.i18n.Tr "repo.release.releases"}} | ||||||
| 			{{if .IsRepositoryWriter}} | 			{{if .CanCreateRelease}} | ||||||
| 				<div class="ui right"> | 				<div class="ui right"> | ||||||
| 					<a class="ui small green button" href="{{$.RepoLink}}/releases/new"> | 					<a class="ui small green button" href="{{$.RepoLink}}/releases/new"> | ||||||
| 						{{.i18n.Tr "repo.release.new_release"}} | 						{{.i18n.Tr "repo.release.new_release"}} | ||||||
| @@ -41,7 +41,7 @@ | |||||||
| 								<a href="{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a> | 								<a href="{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}" rel="nofollow"><i class="tag icon"></i> {{.TagName}}</a> | ||||||
| 							</h4> | 							</h4> | ||||||
| 							<div class="download"> | 							<div class="download"> | ||||||
| 							{{if $.Repository.UnitEnabled $.UnitTypeCode}} | 							{{if $.Permission.CanRead $.UnitTypeCode}} | ||||||
| 								<a href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a> | 								<a href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow"><i class="code icon"></i> {{ShortSha .Sha1}}</a> | ||||||
| 								<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> ZIP</a> | 								<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.zip" rel="nofollow"><i class="octicon octicon-file-zip"></i> ZIP</a> | ||||||
| 								<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a> | 								<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.tar.gz"><i class="octicon octicon-file-zip"></i> TAR.GZ</a> | ||||||
| @@ -50,7 +50,7 @@ | |||||||
| 						{{else}} | 						{{else}} | ||||||
| 							<h3> | 							<h3> | ||||||
| 								<a href="{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}">{{.Title}}</a> | 								<a href="{{$.RepoLink}}/src/tag/{{.TagName | EscapePound}}">{{.Title}}</a> | ||||||
| 								{{if $.IsRepositoryWriter}}<small>(<a href="{{$.RepoLink}}/releases/edit/{{.TagName | EscapePound}}" rel="nofollow">{{$.i18n.Tr "repo.release.edit"}}</a>)</small>{{end}} | 								{{if $.CanCreateRelease}}<small>(<a href="{{$.RepoLink}}/releases/edit/{{.TagName | EscapePound}}" rel="nofollow">{{$.i18n.Tr "repo.release.edit"}}</a>)</small>{{end}} | ||||||
| 							</h3> | 							</h3> | ||||||
| 							<p class="text grey"> | 							<p class="text grey"> | ||||||
| 								<span class="author"> | 								<span class="author"> | ||||||
| @@ -66,7 +66,7 @@ | |||||||
| 							<div class="download"> | 							<div class="download"> | ||||||
| 								<h2>{{$.i18n.Tr "repo.release.downloads"}}</h2> | 								<h2>{{$.i18n.Tr "repo.release.downloads"}}</h2> | ||||||
| 								<ul class="list"> | 								<ul class="list"> | ||||||
| 									{{if $.Repository.UnitEnabled $.UnitTypeCode}} | 									{{if $.Permission.CanRead $.UnitTypeCode}} | ||||||
| 									<li> | 									<li> | ||||||
| 										<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.zip" rel="nofollow"><strong><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (ZIP)</strong></a> | 										<a href="{{$.RepoLink}}/archive/{{.TagName | EscapePound}}.zip" rel="nofollow"><strong><i class="octicon octicon-file-zip"></i> {{$.i18n.Tr "repo.release.source_code"}} (ZIP)</strong></a> | ||||||
| 									</li> | 									</li> | ||||||
|   | |||||||
| @@ -266,7 +266,7 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 		{{end}} | 		{{end}} | ||||||
|  |  | ||||||
| 		{{if .IsRepositoryOwner}} | 		{{if .Permission.IsOwner}} | ||||||
| 		<h4 class="ui top attached warning header"> | 		<h4 class="ui top attached warning header"> | ||||||
| 			{{.i18n.Tr "repo.settings.danger_zone"}} | 			{{.i18n.Tr "repo.settings.danger_zone"}} | ||||||
| 		</h4> | 		</h4> | ||||||
| @@ -294,7 +294,7 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
| 			{{if .Repository.UnitEnabled $.UnitTypeWiki}} | 			{{if .Permission.CanRead $.UnitTypeWiki}} | ||||||
| 				<div class="ui divider"></div> | 				<div class="ui divider"></div> | ||||||
|  |  | ||||||
| 				<div class="item"> | 				<div class="item"> | ||||||
| @@ -324,7 +324,7 @@ | |||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| {{if .IsRepositoryOwner}} | {{if .Permission.IsOwner}} | ||||||
| 	{{if .Repository.IsMirror}} | 	{{if .Repository.IsMirror}} | ||||||
| 	<div class="ui small modal" id="convert-repo-modal"> | 	<div class="ui small modal" id="convert-repo-modal"> | ||||||
| 		<div class="header"> | 		<div class="header"> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| {{if .PageIsSettingsHooksEdit}} | {{if .PageIsSettingsHooksEdit}} | ||||||
| 	<h4 class="ui top attached header"> | 	<h4 class="ui top attached header"> | ||||||
| 		{{.i18n.Tr "repo.settings.recent_deliveries"}} | 		{{.i18n.Tr "repo.settings.recent_deliveries"}} | ||||||
| 		{{if .IsRepositoryAdmin}} | 		{{if .Permission.IsAdmin}} | ||||||
| 			<div class="ui right"> | 			<div class="ui right"> | ||||||
| 				<button class="ui teal tiny button poping up" id="test-delivery" data-content= | 				<button class="ui teal tiny button poping up" id="test-delivery" data-content= | ||||||
| 				"{{.i18n.Tr "repo.settings.webhook.test_delivery_desc"}}" data-variation="inverted tiny" data-link="{{.Link}}/test" data-redirect="{{.Link}}">{{.i18n.Tr "repo.settings.webhook.test_delivery"}}</button> | 				"{{.i18n.Tr "repo.settings.webhook.test_delivery_desc"}}" data-variation="inverted tiny" data-link="{{.Link}}/test" data-redirect="{{.Link}}">{{.i18n.Tr "repo.settings.webhook.test_delivery"}}</button> | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| <div class="ui segment sub-menu"> | <div class="ui segment sub-menu"> | ||||||
| 	<div class="ui two horizontal center link list"> | 	<div class="ui two horizontal center link list"> | ||||||
| 		{{if and (.Repository.UnitEnabled $.UnitTypeCode) (not .IsBareRepo)}} | 		{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsBareRepo)}} | ||||||
| 			<div class="item{{if .PageIsCommits}} active{{end}}"> | 			<div class="item{{if .PageIsCommits}} active{{end}}"> | ||||||
| 				<a href="{{.RepoLink}}/commits{{if .IsViewBranch}}/branch{{else if .IsViewTag}}/tag{{else if .IsViewCommit}}/commit{{end}}/{{EscapePound .BranchName}}"><i class="octicon octicon-history"></i> <b>{{.CommitsCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .CommitsCount "repo.commit" "repo.commits") }}</a> | 				<a href="{{.RepoLink}}/commits{{if .IsViewBranch}}/branch{{else if .IsViewTag}}/tag{{else if .IsViewCommit}}/commit{{end}}/{{EscapePound .BranchName}}"><i class="octicon octicon-history"></i> <b>{{.CommitsCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .CommitsCount "repo.commit" "repo.commits") }}</a> | ||||||
| 			</div> | 			</div> | ||||||
| 		{{end}} | 		{{end}} | ||||||
| 		{{if and (.Repository.UnitEnabled $.UnitTypeCode) (not .IsBareRepo) }} | 		{{if and (.Permission.CanRead $.UnitTypeCode) (not .IsBareRepo) }} | ||||||
| 			<div class="item{{if .PageIsBranches}} active{{end}}"> | 			<div class="item{{if .PageIsBranches}} active{{end}}"> | ||||||
| 				<a href="{{.RepoLink}}/branches/"><i class="octicon octicon-git-branch"></i> <b>{{.BranchesCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .BranchesCount "repo.branch" "repo.branches") }}</a> | 				<a href="{{.RepoLink}}/branches/"><i class="octicon octicon-git-branch"></i> <b>{{.BranchesCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .BranchesCount "repo.branch" "repo.branches") }}</a> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
| 	<div class="ui container"> | 	<div class="ui container"> | ||||||
| 		<div class="ui header"> | 		<div class="ui header"> | ||||||
| 			{{.i18n.Tr "repo.wiki.pages"}} | 			{{.i18n.Tr "repo.wiki.pages"}} | ||||||
| 			{{if and .IsRepositoryWriter (not .IsRepositoryMirror)}} | 			{{if and .CanWriteWiki (not .IsRepositoryMirror)}} | ||||||
| 			<div class="ui right"> | 			<div class="ui right"> | ||||||
| 				<a class="ui green small button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.new_page_button"}}</a> | 				<a class="ui green small button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.new_page_button"}}</a> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ | |||||||
| 			<span class="mega-octicon octicon-book"></span> | 			<span class="mega-octicon octicon-book"></span> | ||||||
| 			<h2>{{.i18n.Tr "repo.wiki.welcome"}}</h2> | 			<h2>{{.i18n.Tr "repo.wiki.welcome"}}</h2> | ||||||
| 			<p>{{.i18n.Tr "repo.wiki.welcome_desc"}}</p> | 			<p>{{.i18n.Tr "repo.wiki.welcome_desc"}}</p> | ||||||
| 			{{if and .IsRepositoryWriter (not .Repository.IsMirror)}} | 			{{if and .CanWriteWiki (not .Repository.IsMirror)}} | ||||||
| 				<a class="ui green button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.create_first_page"}}</a> | 				<a class="ui green button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.create_first_page"}}</a> | ||||||
| 			{{end}} | 			{{end}} | ||||||
| 		</div> | 		</div> | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ | |||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="eight wide right aligned column"> | 				<div class="eight wide right aligned column"> | ||||||
| 					{{if and .IsRepositoryWriter (not .Repository.IsMirror)}} | 					{{if and .CanWriteWiki (not .Repository.IsMirror)}} | ||||||
| 						<div class="ui right"> | 						<div class="ui right"> | ||||||
| 							<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}/_edit">{{.i18n.Tr "repo.wiki.edit_page_button"}}</a> | 							<a class="ui small button" href="{{.RepoLink}}/wiki/{{.PageURL}}/_edit">{{.i18n.Tr "repo.wiki.edit_page_button"}}</a> | ||||||
| 							<a class="ui green small button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.new_page_button"}}</a> | 							<a class="ui green small button" href="{{.RepoLink}}/wiki/_new">{{.i18n.Tr "repo.wiki.new_page_button"}}</a> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao