Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Restrict permission check on repositories and fix some problems #5314

Merged
merged 32 commits into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0bf41c2
fix units permission problems
lunny Nov 10, 2018
15f80b9
fix some bugs and merge LoadUnits to repoAssignment
lunny Nov 11, 2018
40d552c
refactor permission struct and add some copyright heads
lunny Nov 11, 2018
d80fea2
remove unused codes
lunny Nov 11, 2018
fb4a2cb
fix routes units check
lunny Nov 11, 2018
a21bfde
improve permission check
lunny Nov 11, 2018
422ba40
add unit tests for permission
lunny Nov 12, 2018
dae595b
fix typo
lunny Nov 12, 2018
6bed0d4
fix tests
lunny Nov 12, 2018
d5ba3a0
fix some routes
lunny Nov 12, 2018
426980d
fix api permission check
lunny Nov 12, 2018
50d1287
improve permission check
lunny Nov 12, 2018
95d9a58
fix some permission check
lunny Nov 13, 2018
5df61b6
fix tests
lunny Nov 13, 2018
4ee6e1f
fix tests
lunny Nov 13, 2018
de04377
improve some permission check
lunny Nov 13, 2018
66fd8f3
fix some permission check
lunny Nov 14, 2018
11bde94
refactor AccessLevel
lunny Nov 17, 2018
ba60cc8
fix bug
lunny Nov 17, 2018
a978acc
fix tests
lunny Nov 17, 2018
d161315
fix tests
lunny Nov 17, 2018
e5e165c
fix tests
lunny Nov 17, 2018
9253015
fix AccessLevel
lunny Nov 18, 2018
2f65f7a
rename CanAccess
lunny Nov 18, 2018
962be78
fix tests
lunny Nov 18, 2018
9742d63
fix comment
lunny Nov 18, 2018
2db05db
fix bug
lunny Nov 18, 2018
3620109
add missing unit for test repos
lunny Nov 18, 2018
861b3b2
fix bug
lunny Nov 18, 2018
b677d04
rename some functions
lunny Nov 18, 2018
79365d8
fix routes check
lunny Nov 18, 2018
d54bc51
Merge branch 'master' into lunny/fix_units_permissions
lunny Nov 28, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix units permission problems
  • Loading branch information
lunny committed Nov 28, 2018
commit 0bf41c2d5042b4d91259321876c61fb060f228e5
10 changes: 8 additions & 2 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,9 @@ type Repository struct {
IsMirror bool `xorm:"INDEX"`
*Mirror `xorm:"-"`

ExternalMetas map[string]string `xorm:"-"`
Units []*RepoUnit `xorm:"-"`
ExternalMetas map[string]string `xorm:"-"`
Units []*RepoUnit `xorm:"-"`
UnitsMode map[UnitType]AccessMode `xorm:"-"`

IsFork bool `xorm:"INDEX NOT NULL DEFAULT false"`
ForkID int64 `xorm:"INDEX"`
Expand Down Expand Up @@ -369,11 +370,16 @@ func (repo *Repository) getUnitsByUserID(e Engine, userID int64, isAdmin bool) (
return err
}

repo.UnitsMode = make(map[UnitType]AccessMode)
// unique
var newRepoUnits = make([]*RepoUnit, 0, len(repo.Units))
for _, u := range repo.Units {
for _, team := range teams {
if team.unitEnabled(e, u.Type) {
m := repo.UnitsMode[u.Type]
if m < team.Authorize {
repo.UnitsMode[u.Type] = m
}
newRepoUnits = append(newRepoUnits, u)
break
}
Expand Down
59 changes: 59 additions & 0 deletions modules/context/permission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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"

// Permission contains all the permissions related variables to a repository
type Permission struct {
AccessMode models.AccessMode
Units []*models.RepoUnit
UnitsMode map[models.UnitType]models.AccessMode
}

// IsOwner returns true if current user is the owner of repository.
func (p *Permission) IsOwner() bool {
return p.AccessMode >= models.AccessModeOwner
}

// IsAdmin returns true if current user has admin or higher access of repository.
func (p *Permission) IsAdmin() bool {
return p.AccessMode >= models.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 >= models.AccessModeRead
}
return len(p.UnitsMode) > 0
}

// UnitAccessMode returns current user accessmode to the specify unit of the repository
func (p *Permission) UnitAccessMode(unitType models.UnitType) models.AccessMode {
if p.UnitsMode == nil {
return p.AccessMode
}
return p.UnitsMode[unitType]
}

// CanAccess returns true if user has read access to the unit of the repository
func (p *Permission) CanAccess(unitType models.UnitType) bool {
return p.UnitAccessMode(unitType) >= models.AccessModeRead
}

// CanWrite returns true if user could write to this unit
func (p *Permission) CanWrite(unitType models.UnitType) bool {
return p.UnitAccessMode(unitType) >= models.AccessModeWrite
}

// 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(models.UnitTypePullRequests)
}
return p.CanWrite(models.UnitTypeIssues)
}
53 changes: 18 additions & 35 deletions modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type PullRequest struct {

// Repository contains information to operate a repository
type Repository struct {
AccessMode models.AccessMode
Permission
IsWatching bool
IsViewBranch bool
IsViewTag bool
Expand All @@ -54,34 +54,14 @@ type Repository struct {
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.
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.
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
Expand All @@ -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?
isAssigned, _ := models.IsUserAssignedToIssue(issue, user)
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.
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
Expand Down Expand Up @@ -223,7 +203,7 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
func repoAssignment(ctx *Context, repo *models.Repository) {
// Admin has super access.
if ctx.IsSigned && ctx.User.IsAdmin {
ctx.Repo.AccessMode = models.AccessModeOwner
ctx.Repo.Permission.AccessMode = models.AccessModeOwner
} else {
var userID int64
if ctx.User != nil {
Expand All @@ -234,11 +214,11 @@ func repoAssignment(ctx *Context, repo *models.Repository) {
ctx.ServerError("AccessLevel", err)
return
}
ctx.Repo.AccessMode = mode
ctx.Repo.Permission.AccessMode = mode
}

// Check access.
if ctx.Repo.AccessMode == models.AccessModeNone {
if ctx.Repo.Permission.AccessMode == models.AccessModeNone {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
Expand Down Expand Up @@ -379,9 +359,10 @@ func RepoAssignment() macaron.Handler {
ctx.Data["Title"] = owner.Name + "/" + repo.Name
ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
ctx.Data["IsRepositoryWriter"] = ctx.Repo.IsWriter()
ctx.Data["IsRepositoryOwner"] = ctx.Repo.Permission.IsOwner()
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.Permission.IsAdmin()
// FIXME: IsRepositoryWriter will only be used on codes related operations.
ctx.Data["IsRepositoryWriter"] = ctx.Repo.Permission.CanWrite(models.UnitTypeCode)

if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
ctx.ServerError("CanUserFork", err)
Expand Down Expand Up @@ -435,7 +416,7 @@ func RepoAssignment() macaron.Handler {
}

// 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
// and base repository accepts pull requests.
if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
Expand Down Expand Up @@ -671,10 +652,10 @@ func RequireRepoAdmin() macaron.Handler {
}
}

// RequireRepoWriter returns a macaron middleware for requiring repository write permission
func RequireRepoWriter() macaron.Handler {
// RequireRepoWriter returns a macaron middleware for requiring repository write to code permission
func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
return func(ctx *Context) {
if !ctx.IsSigned || (!ctx.Repo.IsWriter() && !ctx.User.IsAdmin) {
if !ctx.IsSigned || (!ctx.Repo.CanWrite(unitType) && !ctx.User.IsAdmin) {
ctx.NotFound(ctx.Req.RequestURI, nil)
return
}
Expand All @@ -698,6 +679,8 @@ func LoadRepoUnits() macaron.Handler {
ctx.ServerError("LoadUnitsByUserID", err)
return
}
ctx.Repo.Permission.Units = ctx.Repo.Repository.Units
ctx.Repo.Permission.UnitsMode = ctx.Repo.Repository.UnitsMode
}
}

Expand Down
24 changes: 13 additions & 11 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func repoAssignment() macaron.Handler {
return
}
repo.Owner = owner
ctx.Repo.Repository = repo
techknowlogick marked this conversation as resolved.
Show resolved Hide resolved

if ctx.IsSigned && ctx.User.IsAdmin {
ctx.Repo.AccessMode = models.AccessModeOwner
Expand All @@ -168,8 +169,6 @@ func repoAssignment() macaron.Handler {
ctx.Status(404)
return
}

ctx.Repo.Repository = repo
}
}

Expand Down Expand Up @@ -205,12 +204,15 @@ func reqAdmin() macaron.Handler {
}
}

func reqRepoWriter() macaron.Handler {
func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
return func(ctx *context.Context) {
if !ctx.Repo.IsWriter() {
ctx.Error(403)
return
for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) {
return
}
}

ctx.Error(403)
}
}

Expand Down Expand Up @@ -453,7 +455,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Delete(repo.DeleteHook)
m.Post("/tests", context.RepoRef(), repo.TestHook)
})
}, reqToken(), reqRepoWriter())
}, reqToken(), reqRepoWriter(models.UnitTypeCode))
m.Group("/collaborators", func() {
m.Get("", repo.ListCollaborators)
m.Combo("/:collaborator").Get(repo.IsCollaborator).
Expand All @@ -473,7 +475,7 @@ func RegisterRoutes(m *macaron.Macaron) {
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
m.Combo("/:id").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey)
}, reqToken(), reqRepoWriter())
}, reqToken(), reqRepoWriter(models.UnitTypeCode))
m.Group("/times", func() {
m.Combo("").Get(repo.ListTrackedTimesByRepository)
m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
Expand Down Expand Up @@ -524,10 +526,10 @@ func RegisterRoutes(m *macaron.Macaron) {
})
m.Group("/milestones", func() {
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).
Patch(reqToken(), reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(), repo.DeleteMilestone)
Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone)
})
m.Get("/stargazers", repo.ListStargazers)
m.Get("/subscribers", repo.ListSubscribers)
Expand Down
8 changes: 4 additions & 4 deletions routers/api/v1/repo/collaborators.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func ListCollaborators(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/UserList"
if !ctx.Repo.IsWriter() {
techknowlogick marked this conversation as resolved.
Show resolved Hide resolved
if !ctx.Repo.CanWrite(models.UnitTypeCode) {
ctx.Error(403, "", "User does not have push access")
return
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func IsCollaborator(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
// "404":
// "$ref": "#/responses/empty"
if !ctx.Repo.IsWriter() {
if !ctx.Repo.CanWrite(models.UnitTypeCode) {
ctx.Error(403, "", "User does not have push access")
return
}
Expand Down Expand Up @@ -133,7 +133,7 @@ func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) {
// responses:
// "204":
// "$ref": "#/responses/empty"
if !ctx.Repo.IsWriter() {
if !ctx.Repo.CanWrite(models.UnitTypeCode) {
ctx.Error(403, "", "User does not have push access")
return
}
Expand Down Expand Up @@ -193,7 +193,7 @@ func DeleteCollaborator(ctx *context.APIContext) {
// responses:
// "204":
// "$ref": "#/responses/empty"
if !ctx.Repo.IsWriter() {
if !ctx.Repo.CanWrite(models.UnitTypeCode) {
ctx.Error(403, "", "User does not have push access")
return
}
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func GetRawFile(ctx *context.APIContext) {
// responses:
// 200:
// description: success
if !ctx.Repo.HasAccess() {
techknowlogick marked this conversation as resolved.
Show resolved Hide resolved
if !ctx.Repo.CanAccess(models.UnitTypeCode) {
ctx.Status(404)
return
}
Expand Down
15 changes: 7 additions & 8 deletions routers/api/v1/repo/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
// "$ref": "#/responses/Issue"

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

Expand All @@ -184,7 +184,7 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {

var assigneeIDs = make([]int64, 0)
var err error
if ctx.Repo.IsWriter() {
if ctx.Repo.CanWrite(models.UnitTypeIssues) {
issue.MilestoneID = form.Milestone
assigneeIDs, err = models.MakeIDsFromAPIAssigneesToAdd(form.Assignee, form.Assignees)
if err != nil {
Expand Down Expand Up @@ -274,7 +274,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}

if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.IsWriter() {
if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403)
return
}
Expand All @@ -288,7 +288,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {

// Update the deadline
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())
}

Expand All @@ -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.
// 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 := ""
if form.Assignee != nil {
oneAssignee = *form.Assignee
Expand All @@ -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 {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
Expand Down Expand Up @@ -403,7 +402,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
return
}

if !ctx.Repo.IsWriter() {
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Status(403)
return
}
Expand Down
Loading