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
release/v1.7
Lunny Xiao 5 years ago committed by GitHub
parent 0222623be9
commit eabbddcd98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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
} }

@ -108,4 +108,116 @@
repo_id: 33 repo_id: 33
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

@ -44,4 +44,22 @@
id: 8 id: 8
org_id: 19 org_id: 19
team_id: 6 team_id: 6
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 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 {
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 { if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err) 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 return true
}
for _, unit := range repo.Units {
if unit.Type == unitType {
return true
}
}
return false
}
// LoadUnitsByUserID loads units according userID's permissions
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
} }
user, err := getUserByID(e, userID)
teams, err := getUserRepoTeams(e, repo.OwnerID, userID, repo.ID)
if err != nil { if err != nil {
return err return false
} }
perm, err := getUserRepoPermission(e, repo, user)
// unique if err != nil {
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units)) return false
for _, u := range repo.Units {
for _, team := range teams {
if team.unitEnabled(e, u.Type) {
newRepoUnits = append(newRepoUnits, u)
break
}
}
} }
repo.Units = newRepoUnits return perm.CanRead(unitType)
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),

@ -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)
}

@ -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)

@ -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 if err != nil {
} else { ctx.ServerError("GetUserRepoPermission", err)
var userID int64 return
if ctx.User != nil {
userID = ctx.User.ID
}
mode, err := models.AccessLevel(userID, repo)
if err != nil {
ctx.ServerError("AccessLevel", err)
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 if err != nil {
} else { ctx.Error(500, "GetUserRepoPermission", err)
mode, err := models.AccessLevel(utils.UserID(ctx), repo) return
if err != nil {
ctx.Error(500, "AccessLevel", err)
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) {
if !ctx.Repo.IsOwner() {
ctx.Error(403)
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) { return func(ctx *context.Context) {
if !ctx.Repo.IsWriter() { if !ctx.Repo.HasAccess() {
ctx.Error(403) ctx.Error(403)
return 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,8 +38,8 @@ func GetRepositoryByOwnerAndName(ctx *macaron.Context) {
ctx.JSON(200, repo) ctx.JSON(200, repo)
} }
//AccessLevel chainload to models.AccessLevel //CheckUnitUser chainload to models.CheckUnitUser
func AccessLevel(ctx *macaron.Context) { func CheckUnitUser(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid") repoID := ctx.ParamsInt64(":repoid")
userID := ctx.ParamsInt64(":userid") userID := ctx.ParamsInt64(":userid")
repo, err := models.GetRepositoryByID(repoID) repo, err := models.GetRepositoryByID(repoID)
@ -49,32 +49,27 @@ func AccessLevel(ctx *macaron.Context) {
}) })
return return
} }
al, err := models.AccessLevel(userID, repo)
if err != nil { var user *models.User
ctx.JSON(500, map[string]interface{}{ if userID > 0 {
"err": err.Error(), user, err = models.GetUserByID(userID)
}) if err != nil {
return ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
} }
ctx.JSON(200, al)
}
//CheckUnitUser chainload to models.CheckUnitUser perm, err := models.GetUserRepoPermission(repo, user)
func CheckUnitUser(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid")
userID := ctx.ParamsInt64(":userid")
repo, err := models.GetRepositoryByID(repoID)
if err != nil { if err != nil {
ctx.JSON(500, map[string]interface{}{ ctx.JSON(500, map[string]interface{}{
"err": err.Error(), "err": err.Error(),
}) })
return return
} }
if repo.CheckUnitUser(userID, ctx.QueryBool("isAdmin"), models.UnitType(ctx.QueryInt("unitType"))) {
ctx.PlainText(200, []byte("success")) ctx.JSON(200, perm.UnitAccessMode(models.UnitType(ctx.QueryInt("unitType"))))
return
}
ctx.PlainText(404, []byte("no access"))
} }
// 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,36 +182,19 @@ 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("GetUserRepoPermission", err)
ctx.ServerError("HasAccess", err) return
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
}
} else {
ctx.HandleText(http.StatusForbidden, "User permission denied")
return
}
}
if !isPull && repo.IsMirror { if !perm.CanAccess(accessMode, unitType) {
ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") ctx.HandleText(http.StatusForbidden, "User permission denied")
return return
}
} }
if !repo.CheckUnitUser(authUser.ID, authUser.IsAdmin, unitType) { if !isPull && repo.IsMirror {
ctx.HandleText(http.StatusForbidden, fmt.Sprintf("User %s does not have allowed access to repository %s 's code", ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
authUser.Name, repo.RepoPath()))
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.
@ -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 {
ctx.ServerError("GetUserByID", err)
return nil, nil, 0
}
perm, err := models.GetUserRepoPermission(repo, user)
if err != nil { if err != nil {
ctx.ServerError("GetUserIfHasWriteAccess", err) 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 if err != nil {
} else if repo.Owner.IsOrganization() { return models.CommentTagNone, err
isOwner, err := repo.Owner.IsOwnedBy(poster.ID)
if err != nil {
return models.CommentTagNone, err
} else if isOwner {
return models.CommentTagOwner, nil
}
} }
if poster.IsWriterOfRepo(repo) { if perm.IsOwner() {
return models.CommentTagWriter, nil return models.CommentTagOwner, 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,13 +766,20 @@ 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 {
// Check if branch is not protected perm, err := models.GetUserRepoPermission(pull.HeadRepo, ctx.User)
if protected, err := pull.HeadRepo.IsProtectedBranch(pull.HeadBranch, ctx.User); err != nil { if err != nil {
log.Error(4, "IsProtectedBranch: %v", err) ctx.ServerError("GetUserRepoPermission", err)
} else if !protected { return
canDelete = true }
ctx.Data["DeleteBranchLink"] = ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index) + "/cleanup" if perm.CanWrite(models.UnitTypeCode) {
// Check if branch is not protected
if protected, err := pull.HeadRepo.IsProtectedBranch(pull.HeadBranch, ctx.User); err != nil {
log.Error(4, "IsProtectedBranch: %v", err)
} else if !protected {
canDelete = true
ctx.Data["DeleteBranchLink"] = ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index) + "/cleanup"
}
} }
} }
} }
@ -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 err != nil { if ctx.Written() {
c.ServerError("watch is not bool", err)
return return
} }
issue := GetActionIssue(c) if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) {
if c.Written() { ctx.Error(403)
return
}
watch, err := strconv.ParseBool(ctx.Req.PostForm.Get("watch"))
if err != nil {
ctx.ServerError("watch is not bool", err)
return return
} }
if err := models.CreateOrUpdateIssueWatch(c.User.ID, issue.ID, watch); err != nil { if err := models.CreateOrUpdateIssueWatch(ctx.User.ID, issue.ID, watch); err != nil {
c.ServerError("CreateOrUpdateIssueWatch", err) ctx.ServerError("CreateOrUpdateIssueWatch", err)
return return
} }
url := fmt.Sprintf("%s/issues/%d", c.Repo.RepoLink, issue.Index) url := fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index)
c.Redirect(url, http.StatusSeeOther) ctx.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>

Loading…
Cancel
Save