diff --git a/cmd/serve.go b/cmd/serve.go index e8e5c186c..9e34b95c5 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -164,6 +164,11 @@ func runServ(c *cli.Context) { println("You have no right to write this repository") log.GitLogger.Fatal(2, "User %s has no right to write repository %s", user.Name, repoPath) } + + if repo.IsMirror { + println("You can't write to a mirror repository") + log.GitLogger.Fatal(2, "User %s tried to write to a mirror repository %s", user.Name, repoPath) + } case isRead: if !repo.IsPrivate { break diff --git a/cmd/web.go b/cmd/web.go index 1b692ceb4..8213baab2 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -318,7 +318,7 @@ func runWeb(ctx *cli.Context) { m.Get("/template/*", dev.TemplatePreview) } - reqTrueOwner := middleware.RequireTrueOwner() + reqAdmin := middleware.RequireAdmin() // Organization. m.Group("/org", func() { @@ -393,7 +393,7 @@ func runWeb(ctx *cli.Context) { m.Post("/:name", repo.GitHooksEditPost) }, middleware.GitHookService()) }) - }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) + }, reqSignIn, middleware.RepoAssignment(true), reqAdmin) m.Group("/:username/:reponame", func() { m.Get("/action/:action", repo.Action) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e69b0a1f5..f6ef51320 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -51,7 +51,8 @@ type Version struct { // update _MIN_VER_DB accordingly var migrations = []Migration{ NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1 - NewMigration("refactor access table to use id's", accessRefactor), // V1 -> V2 + NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2 + NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3 } // Migrate database to current version @@ -212,31 +213,91 @@ func accessToCollaboration(x *xorm.Engine) (err error) { return sess.Commit() } +func ownerTeamUpdate(x *xorm.Engine) (err error) { + if _, err := x.Exec("UPDATE team SET authorize=4 WHERE lower_name=?", "owners"); err != nil { + return fmt.Errorf("drop table: %v", err) + } + return nil +} + func accessRefactor(x *xorm.Engine) (err error) { type ( AccessMode int Access struct { - ID int64 `xorm:"pk autoincr"` - UserName string - RepoName string - UserID int64 `xorm:"UNIQUE(s)"` - RepoID int64 `xorm:"UNIQUE(s)"` - Mode AccessMode + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + Mode AccessMode + } + UserRepo struct { + UserID int64 + RepoID int64 } ) - var rawSQL string - switch { - case setting.UseSQLite3, setting.UsePostgreSQL: - rawSQL = "DROP INDEX IF EXISTS `UQE_access_S`" - case setting.UseMySQL: - rawSQL = "DROP INDEX `UQE_access_S` ON `access`" + // We consiously don't start a session yet as we make only reads for now, no writes + + accessMap := make(map[UserRepo]AccessMode, 50) + + results, err := x.Query("SELECT r.id as `repo_id`, r.is_private as `is_private`, r.owner_id as `owner_id`, u.type as `owner_type` FROM `repository` r LEFT JOIN user u ON r.owner_id=u.id") + if err != nil { + return err } - if _, err = x.Exec(rawSQL); err != nil && - !strings.Contains(err.Error(), "check that column/key exists") { - return fmt.Errorf("drop index: %v", err) + for _, repo := range results { + repoID := com.StrTo(repo["repo_id"]).MustInt64() + isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0 + ownerID := com.StrTo(repo["owner_id"]).MustInt64() + ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0 + + results, err := x.Query("SELECT user_id FROM collaboration WHERE repo_id=?", repoID) + if err != nil { + return fmt.Errorf("select repos: %v", err) + } + for _, user := range results { + userID := com.StrTo(user["user_id"]).MustInt64() + accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS + } + + if !ownerIsOrganization { + continue + } + + minAccessLevel := AccessMode(0) + if !isPrivate { + minAccessLevel = 1 + } + + repoString := "$" + string(repo["repo_id"]) + "|" + + results, err = x.Query("SELECT id, authorize, repo_ids FROM team WHERE org_id=? AND authorize > ? ORDER BY authorize ASC", ownerID, int(minAccessLevel)) + if err != nil { + return fmt.Errorf("select teams from org: %v", err) + } + + for _, team := range results { + if !strings.Contains(string(team["repo_ids"]), repoString) { + continue + } + teamID := com.StrTo(team["id"]).MustInt64() + mode := AccessMode(com.StrTo(team["authorize"]).MustInt()) + + results, err := x.Query("SELECT uid FROM team_user WHERE team_id=?", teamID) + if err != nil { + return fmt.Errorf("select users from team: %v", err) + } + for _, user := range results { + userID := com.StrTo(user["uid"]).MustInt64() + accessMap[UserRepo{userID, repoID}] = mode + } + } } + // Drop table can't be in a session (at least not in sqlite) + if _, err = x.Exec("DROP TABLE access"); err != nil { + return fmt.Errorf("drop table: %v", err) + } + + // Now we start writing so we make a session sess := x.NewSession() defer sessionRelease(sess) if err = sess.Begin(); err != nil { @@ -247,55 +308,12 @@ func accessRefactor(x *xorm.Engine) (err error) { return fmt.Errorf("sync: %v", err) } - accesses := make([]*Access, 0, 50) - if err = sess.Iterate(new(Access), func(idx int, bean interface{}) error { - a := bean.(*Access) - - // Update username to user ID. - users, err := sess.Query("SELECT `id` FROM `user` WHERE lower_name=?", a.UserName) - if err != nil { - return fmt.Errorf("query user: %v", err) - } else if len(users) < 1 { - return nil - } - a.UserID = com.StrTo(users[0]["id"]).MustInt64() - - // Update repository name(username/reponame) to repository ID. - names := strings.Split(a.RepoName, "/") - ownerName := names[0] - repoName := names[1] - - // Check if user is the owner of the repository. - ownerID := a.UserID - if ownerName != a.UserName { - users, err := sess.Query("SELECT `id` FROM `user` WHERE lower_name=?", ownerName) - if err != nil { - return fmt.Errorf("query owner: %v", err) - } else if len(users) < 1 { - return nil - } - ownerID = com.StrTo(users[0]["id"]).MustInt64() - } - - repos, err := sess.Query("SELECT `id` FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName) - if err != nil { - return fmt.Errorf("query repository: %v", err) - } else if len(repos) < 1 { - return nil - } - a.RepoID = com.StrTo(repos[0]["id"]).MustInt64() - - accesses = append(accesses, a) - return nil - }); err != nil { - return fmt.Errorf("iterate: %v", err) + accesses := make([]*Access, 0, len(accessMap)) + for ur, mode := range accessMap { + accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode}) } - for i := range accesses { - if _, err = sess.Id(accesses[i].ID).Update(accesses[i]); err != nil { - return fmt.Errorf("update: %v", err) - } - } + _, err = sess.Insert(accesses) return sess.Commit() } diff --git a/models/org.go b/models/org.go index 36cf5f7d1..8cb17b605 100644 --- a/models/org.go +++ b/models/org.go @@ -666,6 +666,11 @@ func UpdateTeam(t *Team, authChanged bool) (err error) { return err } + t.LowerName = strings.ToLower(t.Name) + if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { + return err + } + // Update access for team members if needed. if authChanged { if err = t.getRepositories(sess); err != nil { @@ -679,10 +684,6 @@ func UpdateTeam(t *Team, authChanged bool) (err error) { } } - t.LowerName = strings.ToLower(t.Name) - if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil { - return err - } return sess.Commit() } diff --git a/modules/middleware/context.go b/modules/middleware/context.go index 45779d580..dc3b5cad1 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -38,29 +38,7 @@ type Context struct { IsSigned bool IsBasicAuth bool - Repo struct { - IsOwner bool - IsTrueOwner bool - IsWatching bool - IsBranch bool - IsTag bool - IsCommit bool - IsAdmin bool // Current user is admin level. - HasAccess bool - Repository *models.Repository - Owner *models.User - Commit *git.Commit - Tag *git.Tag - GitRepo *git.Repository - BranchName string - TagName string - TreeName string - CommitId string - RepoLink string - CloneLink models.CloneLink - CommitsCount int - Mirror *models.Mirror - } + Repo RepoContext Org struct { IsOwner bool @@ -73,6 +51,37 @@ type Context struct { } } +type RepoContext struct { + AccessMode models.AccessMode + IsWatching bool + IsBranch bool + IsTag bool + IsCommit bool + Repository *models.Repository + Owner *models.User + Commit *git.Commit + Tag *git.Tag + GitRepo *git.Repository + BranchName string + TagName string + TreeName string + CommitId string + RepoLink string + CloneLink models.CloneLink + CommitsCount int + Mirror *models.Mirror +} + +// Return if the current user has write access for this repository +func (r RepoContext) IsOwner() bool { + return r.AccessMode >= models.ACCESS_MODE_WRITE +} + +// Return if the current user has read access for this repository +func (r RepoContext) HasAccess() bool { + return r.AccessMode >= models.ACCESS_MODE_READ +} + // HasError returns true if error occurs in form validation. func (ctx *Context) HasApiError() bool { hasErr, ok := ctx.Data["HasError"] diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index 8188deb53..3350c03d2 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -58,24 +58,19 @@ func ApiRepoAssignment() macaron.Handler { return } - if ctx.IsSigned { - mode, err := models.AccessLevel(ctx.User, repo) - if err != nil { - ctx.JSON(500, &base.ApiJsonErr{"AccessLevel: " + err.Error(), base.DOC_URL}) - return - } - - ctx.Repo.IsOwner = mode >= models.ACCESS_MODE_WRITE - ctx.Repo.IsAdmin = mode >= models.ACCESS_MODE_READ - ctx.Repo.IsTrueOwner = mode >= models.ACCESS_MODE_OWNER + mode, err := models.AccessLevel(ctx.User, repo) + if err != nil { + ctx.JSON(500, &base.ApiJsonErr{"AccessLevel: " + err.Error(), base.DOC_URL}) + return } + ctx.Repo.AccessMode = mode + // Check access. - if repo.IsPrivate && !ctx.Repo.IsOwner { + if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE { ctx.Error(404) return } - ctx.Repo.HasAccess = true ctx.Repo.Repository = repo } @@ -239,26 +234,18 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { return } - if ctx.IsSigned { - mode, err := models.AccessLevel(ctx.User, repo) - if err != nil { - ctx.Handle(500, "AccessLevel", err) - return - } - ctx.Repo.IsOwner = mode >= models.ACCESS_MODE_WRITE - ctx.Repo.IsAdmin = mode >= models.ACCESS_MODE_READ - ctx.Repo.IsTrueOwner = mode >= models.ACCESS_MODE_OWNER - if !ctx.Repo.IsTrueOwner && ctx.Repo.Owner.IsOrganization() { - ctx.Repo.IsTrueOwner = ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) - } + mode, err := models.AccessLevel(ctx.User, repo) + if err != nil { + ctx.Handle(500, "AccessLevel", err) + return } + ctx.Repo.AccessMode = mode // Check access. - if repo.IsPrivate && !ctx.Repo.IsOwner { + if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE { ctx.Handle(404, "no access right", err) return } - ctx.Repo.HasAccess = true ctx.Data["HasAccess"] = true @@ -306,8 +293,8 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { ctx.Data["Title"] = u.Name + "/" + repo.Name ctx.Data["Repository"] = repo ctx.Data["Owner"] = ctx.Repo.Repository.Owner - ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner - ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner + ctx.Data["IsRepositoryOwner"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_WRITE + ctx.Data["IsRepositoryAdmin"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_ADMIN ctx.Data["DisableSSH"] = setting.DisableSSH ctx.Repo.CloneLink, err = repo.CloneLink() @@ -361,9 +348,9 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { } } -func RequireTrueOwner() macaron.Handler { +func RequireAdmin() macaron.Handler { return func(ctx *Context) { - if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin { + if ctx.Repo.AccessMode < models.ACCESS_MODE_ADMIN { if !ctx.IsSigned { ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) ctx.Redirect(setting.AppSubUrl + "/user/login") diff --git a/routers/api/v1/repo_file.go b/routers/api/v1/repo_file.go index a049904f9..73f97b2ca 100644 --- a/routers/api/v1/repo_file.go +++ b/routers/api/v1/repo_file.go @@ -12,7 +12,7 @@ import ( ) func GetRepoRawFile(ctx *middleware.Context) { - if ctx.Repo.Repository.IsPrivate && !ctx.Repo.HasAccess { + if !ctx.Repo.HasAccess() { ctx.Error(404) return } diff --git a/routers/repo/http.go b/routers/repo/http.go index 034b5a7b5..d47d73ef0 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -158,6 +158,11 @@ func Http(ctx *middleware.Context) { return } } + + if !isPull && repo.IsMirror { + ctx.Handle(401, "can't push to mirror", nil) + return + } } } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index bf39d9aba..40e933897 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -230,7 +230,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) { } // Only collaborators can assign. - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { form.AssigneeId = 0 } issue := &models.Issue{ @@ -434,7 +434,7 @@ func ViewIssue(ctx *middleware.Context) { ctx.Data["Title"] = issue.Name ctx.Data["Issue"] = issue ctx.Data["Comments"] = comments - ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id) + ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterId == ctx.User.Id) ctx.Data["IsRepoToolbarIssues"] = true ctx.Data["IsRepoToolbarIssuesList"] = false ctx.HTML(200, ISSUE_VIEW) @@ -457,7 +457,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { return } - if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { + if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -484,7 +484,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) { } func UpdateIssueLabel(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -560,7 +560,7 @@ func UpdateIssueLabel(ctx *middleware.Context) { } func UpdateIssueMilestone(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -606,7 +606,7 @@ func UpdateIssueMilestone(ctx *middleware.Context) { } func UpdateAssignee(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(403) return } @@ -752,7 +752,7 @@ func Comment(ctx *middleware.Context) { // Check if issue owner changes the status of issue. var newStatus string - if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { + if ctx.Repo.IsOwner() || issue.PosterId == ctx.User.Id { newStatus = ctx.Query("change_status") } if len(newStatus) > 0 { diff --git a/routers/repo/release.go b/routers/repo/release.go index 591810cc5..52d78b196 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -41,7 +41,7 @@ func Releases(ctx *middleware.Context) { tags := make([]*models.Release, len(rawTags)) for i, rawTag := range rawTags { for j, rel := range rels { - if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner) { + if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner()) { continue } if rel.TagName == rawTag { @@ -140,7 +140,7 @@ func Releases(ctx *middleware.Context) { } func NewRelease(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesNew", nil) return } @@ -153,7 +153,7 @@ func NewRelease(ctx *middleware.Context) { } func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesNew", nil) return } @@ -211,7 +211,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { } func EditRelease(ctx *middleware.Context) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.ReleasesEdit", nil) return } @@ -234,7 +234,7 @@ func EditRelease(ctx *middleware.Context) { } func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) { - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Handle(403, "release.EditReleasePost", nil) return } diff --git a/routers/repo/repo.go b/routers/repo/repo.go index dfd827bbb..6b84a389d 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -349,7 +349,7 @@ func Action(ctx *middleware.Context) { case "unstar": err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) case "desc": - if !ctx.Repo.IsOwner { + if !ctx.Repo.IsOwner() { ctx.Error(404) return } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index a0b927be6..21f9cea88 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -49,7 +49,7 @@