Merge branch 'access' of github.com:gogits/gogs into dev

This commit is contained in:
Unknwon 2015-02-28 21:50:29 -05:00
commit b0b11fd7b1
36 changed files with 1385 additions and 1320 deletions

View File

@ -1,6 +1,6 @@
[run] [run]
init_cmds = [ init_cmds = [
["grep", "-rn", "FIXME", "."], #["grep", "-rn", "FIXME", "."],
["./gogs", "web"] ["./gogs", "web"]
] ]
watch_all = true watch_all = true

View File

@ -7,13 +7,13 @@ Gogs (Go Git Service) is a painless self-hosted Git service written in Go.
![Demo](http://gogs.qiniudn.com/gogs_demo.gif) ![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
##### Current version: 0.5.13 Beta ##### Current version: 0.5.16 Beta
### NOTICES ### NOTICES
- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site. - Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
- The demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch. - The demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch.
- You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing a issue or making a Pull Request. - You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request.
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks! - If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
#### Other language version #### Other language version
@ -57,7 +57,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
## System Requirements ## System Requirements
- A cheap Raspberry Pi is powerful enough for basic functionality. - A cheap Raspberry Pi is powerful enough for basic functionality.
- At least 4 CPU cores and 1GB RAM would be the baseline for teamwork. - At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
## Installation ## Installation

View File

@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
![Demo](http://gogs.qiniudn.com/gogs_demo.gif) ![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
##### 当前版本0.5.13 Beta ##### 当前版本0.5.16 Beta
## 开发目的 ## 开发目的
@ -44,7 +44,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
## 系统要求 ## 系统要求
- 最低的系统硬件要求为一个廉价的树莓派 - 最低的系统硬件要求为一个廉价的树莓派
- 如果用于团队项目,建议使用 4 核 CPU 及 1GB 内存 - 如果用于团队项目,建议使用 2 核 CPU 及 1GB 内存
## 安装部署 ## 安装部署

View File

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -43,7 +42,7 @@ func setup(logPath string) {
models.LoadModelsConfig() models.LoadModelsConfig()
if models.UseSQLite3 { if setting.UseSQLite3 {
workDir, _ := setting.WorkDir() workDir, _ := setting.WorkDir()
os.Chdir(workDir) os.Chdir(workDir)
} }
@ -67,33 +66,33 @@ func parseCmd(cmd string) (string, string) {
} }
var ( var (
COMMANDS_READONLY = map[string]models.AccessType{ COMMANDS_READONLY = map[string]models.AccessMode{
"git-upload-pack": models.WRITABLE, "git-upload-pack": models.ACCESS_MODE_WRITE,
"git upload-pack": models.WRITABLE, "git upload-pack": models.ACCESS_MODE_WRITE,
"git-upload-archive": models.WRITABLE, "git-upload-archive": models.ACCESS_MODE_WRITE,
} }
COMMANDS_WRITE = map[string]models.AccessType{ COMMANDS_WRITE = map[string]models.AccessMode{
"git-receive-pack": models.READABLE, "git-receive-pack": models.ACCESS_MODE_READ,
"git receive-pack": models.READABLE, "git receive-pack": models.ACCESS_MODE_READ,
} }
) )
func In(b string, sl map[string]models.AccessType) bool { func In(b string, sl map[string]models.AccessMode) bool {
_, e := sl[b] _, e := sl[b]
return e return e
} }
func runServ(k *cli.Context) { func runServ(c *cli.Context) {
if k.IsSet("config") { if c.IsSet("config") {
setting.CustomConf = k.String("config") setting.CustomConf = c.String("config")
} }
setup("serv.log") setup("serv.log")
if len(k.Args()) < 1 { if len(c.Args()) < 1 {
log.GitLogger.Fatal(2, "Not enough arguments") log.GitLogger.Fatal(2, "Not enough arguments")
} }
keys := strings.Split(k.Args()[0], "-") keys := strings.Split(c.Args()[0], "-")
if len(keys) != 2 { if len(keys) != 2 {
println("Gogs: auth file format error") println("Gogs: auth file format error")
log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2]) log.GitLogger.Fatal(2, "Invalid auth file format: %s", os.Args[2])
@ -117,6 +116,7 @@ func runServ(k *cli.Context) {
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
if cmd == "" { if cmd == "" {
println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.")
println("If this is what you do not expect, please log in with password and setup Gogs under another user.")
return return
} }
@ -144,17 +144,6 @@ func runServ(k *cli.Context) {
} }
// Access check. // Access check.
switch {
case isWrite:
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.WRITABLE)
if err != nil {
println("Gogs: internal error:", err.Error())
log.GitLogger.Fatal(2, "Fail to check write access:", err)
} else if !has {
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)
}
case isRead:
repo, err := models.GetRepositoryByName(repoUser.Id, repoName) repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
if err != nil { if err != nil {
if err == models.ErrRepoNotExist { if err == models.ErrRepoNotExist {
@ -165,11 +154,27 @@ func runServ(k *cli.Context) {
log.GitLogger.Fatal(2, "Fail to get repository: %v", err) log.GitLogger.Fatal(2, "Fail to get repository: %v", err)
} }
switch {
case isWrite:
has, err := models.HasAccess(user, repo, models.ACCESS_MODE_WRITE)
if err != nil {
println("Gogs: internal error:", err.Error())
log.GitLogger.Fatal(2, "Fail to check write access:", err)
} else if !has {
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 { if !repo.IsPrivate {
break break
} }
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.READABLE) has, err := models.HasAccess(user, repo, models.ACCESS_MODE_READ)
if err != nil { if err != nil {
println("Gogs: internal error:", err.Error()) println("Gogs: internal error:", err.Error())
log.GitLogger.Fatal(2, "Fail to check read access:", err) log.GitLogger.Fatal(2, "Fail to check read access:", err)

View File

@ -318,7 +318,7 @@ func runWeb(ctx *cli.Context) {
m.Get("/template/*", dev.TemplatePreview) m.Get("/template/*", dev.TemplatePreview)
} }
reqTrueOwner := middleware.RequireTrueOwner() reqAdmin := middleware.RequireAdmin()
// Organization. // Organization.
m.Group("/org", func() { m.Group("/org", func() {
@ -393,7 +393,7 @@ func runWeb(ctx *cli.Context) {
m.Post("/:name", repo.GitHooksEditPost) m.Post("/:name", repo.GitHooksEditPost)
}, middleware.GitHookService()) }, middleware.GitHookService())
}) })
}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner) }, reqSignIn, middleware.RepoAssignment(true), reqAdmin)
m.Group("/:username/:reponame", func() { m.Group("/:username/:reponame", func() {
m.Get("/action/:action", repo.Action) m.Get("/action/:action", repo.Action)

View File

@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
const APP_VER = "0.5.14.0222 Beta" const APP_VER = "0.5.16.0228 Beta"
func init() { func init() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())

View File

@ -5,76 +5,190 @@
package models package models
import ( import (
"strings" "fmt"
"time"
"github.com/go-xorm/xorm"
) )
type AccessType int type AccessMode int
const ( const (
READABLE AccessType = iota + 1 ACCESS_MODE_NONE AccessMode = iota
WRITABLE ACCESS_MODE_READ
ACCESS_MODE_WRITE
ACCESS_MODE_ADMIN
ACCESS_MODE_OWNER
) )
// Access represents the accessibility of user to repository. // Access represents the highest access level of a user to the repository. The only access type
// that is not in this table is the real owner of a repository. In case of an organization
// repository, the members of the owners team are in this table.
type Access struct { type Access struct {
Id int64 ID int64 `xorm:"pk autoincr"`
UserName string `xorm:"UNIQUE(s)"` UserID int64 `xorm:"UNIQUE(s)"`
RepoName string `xorm:"UNIQUE(s)"` // <user name>/<repo name> RepoID int64 `xorm:"UNIQUE(s)"`
Mode AccessType `xorm:"UNIQUE(s)"` Mode AccessMode
Created time.Time `xorm:"CREATED"`
} }
// AddAccess adds new access record. func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
func AddAccess(access *Access) error { mode := ACCESS_MODE_NONE
access.UserName = strings.ToLower(access.UserName) if !repo.IsPrivate {
access.RepoName = strings.ToLower(access.RepoName) mode = ACCESS_MODE_READ
_, err := x.Insert(access) }
return err
if u != nil {
if u.Id == repo.OwnerId {
return ACCESS_MODE_OWNER, nil
}
a := &Access{UserID: u.Id, RepoID: repo.Id}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
return a.Mode, nil
}
return mode, nil
} }
// UpdateAccess updates access information. // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
func UpdateAccess(access *Access) error { // user does not have access. User can be nil!
access.UserName = strings.ToLower(access.UserName) func AccessLevel(u *User, repo *Repository) (AccessMode, error) {
access.RepoName = strings.ToLower(access.RepoName) return accessLevel(x, u, repo)
_, err := x.Id(access.Id).Update(access)
return err
} }
// DeleteAccess deletes access record. func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
func DeleteAccess(access *Access) error { mode, err := accessLevel(e, u, repo)
_, err := x.Delete(access) return testMode <= mode, err
return err
} }
// UpdateAccess updates access information with session for rolling back. // HasAccess returns true if someone has the request access level. User can be nil!
func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
if _, err := sess.Id(access.Id).Update(access); err != nil { return hasAccess(x, u, repo, testMode)
sess.Rollback() }
return err
// GetAccessibleRepositories finds all repositories where a user has access to,
// besides his own.
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
return nil, err
}
repos := make(map[*Repository]AccessMode, len(accesses))
for _, access := range accesses {
repo, err := GetRepositoryById(access.RepoID)
if err != nil {
return nil, err
}
if err = repo.GetOwner(); err != nil {
return nil, err
} else if repo.OwnerId == u.Id {
continue
}
repos[repo] = access.Mode
}
// FIXME: should we generate an ordered list here? Random looks weird.
return repos, nil
}
func maxAccessMode(modes ...AccessMode) AccessMode {
max := ACCESS_MODE_NONE
for _, mode := range modes {
if mode > max {
max = mode
}
}
return max
}
// FIXME: do corss-comparison so reduce deletions and additions to the minimum?
func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) {
minMode := ACCESS_MODE_READ
if !repo.IsPrivate {
minMode = ACCESS_MODE_WRITE
}
newAccesses := make([]Access, 0, len(accessMap))
for userID, mode := range accessMap {
if mode < minMode {
continue
}
newAccesses = append(newAccesses, Access{
UserID: userID,
RepoID: repo.Id,
Mode: mode,
})
}
// Delete old accesses and insert new ones for repository.
if _, err = e.Delete(&Access{RepoID: repo.Id}); err != nil {
return fmt.Errorf("delete old accesses: %v", err)
} else if _, err = e.Insert(newAccesses); err != nil {
return fmt.Errorf("insert new accesses: %v", err)
} }
return nil return nil
} }
// HasAccess returns true if someone can read or write to given repository. // FIXME: should be able to have read-only access.
// The repoName should be in format <username>/<reponame>. // Give all collaborators write access.
func HasAccess(uname, repoName string, mode AccessType) (bool, error) { func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error {
if len(repoName) == 0 { collaborators, err := repo.getCollaborators(e)
return false, nil
}
access := &Access{
UserName: strings.ToLower(uname),
RepoName: strings.ToLower(repoName),
}
has, err := x.Get(access)
if err != nil { if err != nil {
return false, err return fmt.Errorf("getCollaborators: %v", err)
} else if !has {
return false, nil
} else if mode > access.Mode {
return false, nil
} }
return true, nil for _, c := range collaborators {
accessMap[c.Id] = ACCESS_MODE_WRITE
}
return nil
}
// recalculateTeamAccesses recalculates new accesses for teams of an organization
// except the team whose ID is given. It is used to assign a team ID when
// remove repository from that team.
func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err error) {
accessMap := make(map[int64]AccessMode, 20)
if err = repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
}
if err = repo.getOwner(e); err != nil {
return err
}
if repo.Owner.IsOrganization() {
if err = repo.Owner.getTeams(e); err != nil {
return err
}
for _, t := range repo.Owner.Teams {
if t.ID == ignTeamID {
continue
}
if t.IsOwnerTeam() {
t.Authorize = ACCESS_MODE_OWNER
}
if err = t.getMembers(e); err != nil {
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
}
for _, m := range t.Members {
accessMap[m.Id] = maxAccessMode(accessMap[m.Id], t.Authorize)
}
}
}
return repo.refreshAccesses(e, accessMap)
}
func (repo *Repository) recalculateAccesses(e Engine) error {
accessMap := make(map[int64]AccessMode, 20)
if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
}
return repo.refreshAccesses(e, accessMap)
}
// RecalculateAccesses recalculates all accesses for repository.
func (r *Repository) RecalculateAccesses() error {
return r.recalculateAccesses(x)
} }

View File

@ -434,46 +434,58 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
return nil return nil
} }
// NewRepoAction adds new action for creating repository. func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
func NewRepoAction(u *User, repo *Repository) (err error) { if err = notifyWatchers(e, &Action{
if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email, ActUserId: u.Id,
OpType: CREATE_REPO, RepoId: repo.Id, RepoUserName: repo.Owner.Name, RepoName: repo.Name, ActUserName: u.Name,
ActEmail: u.Email,
OpType: CREATE_REPO,
RepoId: repo.Id,
RepoUserName: repo.Owner.Name,
RepoName: repo.Name,
IsPrivate: repo.IsPrivate}); err != nil { IsPrivate: repo.IsPrivate}); err != nil {
log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name) return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.Id)
return err
} }
log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name) log.Trace("action.NewRepoAction: %s/%s", u.Name, repo.Name)
return err return err
} }
// TransferRepoAction adds new action for transferring repository. // NewRepoAction adds new action for creating repository.
func TransferRepoAction(u, newUser *User, repo *Repository) (err error) { func NewRepoAction(u *User, repo *Repository) (err error) {
return newRepoAction(x, u, repo)
}
func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
action := &Action{ action := &Action{
ActUserId: u.Id, ActUserId: actUser.Id,
ActUserName: u.Name, ActUserName: actUser.Name,
ActEmail: u.Email, ActEmail: actUser.Email,
OpType: TRANSFER_REPO, OpType: TRANSFER_REPO,
RepoId: repo.Id, RepoId: repo.Id,
RepoUserName: newUser.Name, RepoUserName: newOwner.Name,
RepoName: repo.Name, RepoName: repo.Name,
IsPrivate: repo.IsPrivate, IsPrivate: repo.IsPrivate,
Content: path.Join(repo.Owner.LowerName, repo.LowerName), Content: path.Join(oldOwner.LowerName, repo.LowerName),
} }
if err = NotifyWatchers(action); err != nil { if err = notifyWatchers(e, action); err != nil {
log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name) return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.Id)
return err
} }
// Remove watch for organization. // Remove watch for organization.
if repo.Owner.IsOrganization() { if repo.Owner.IsOrganization() {
if err = WatchRepo(repo.Owner.Id, repo.Id, false); err != nil { if err = watchRepo(e, repo.Owner.Id, repo.Id, false); err != nil {
log.Error(4, "WatchRepo", err) return fmt.Errorf("watch repository: %v", err)
} }
} }
log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name) log.Trace("action.TransferRepoAction: %s/%s", actUser.Name, repo.Name)
return err return nil
}
// TransferRepoAction adds new action for transferring repository.
func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
return transferRepoAction(x, actUser, oldOwner, newOwner, repo)
} }
// GetFeeds returns action list of given user in given context. // GetFeeds returns action list of given user in given context.

View File

@ -282,30 +282,33 @@ type IssueUser struct {
} }
// NewIssueUserPairs adds new issue-user pairs for new issue of repository. // NewIssueUserPairs adds new issue-user pairs for new issue of repository.
func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) { func NewIssueUserPairs(repo *Repository, issueID, orgID, posterID, assigneeID int64) (err error) {
iu := &IssueUser{IssueId: iid, RepoId: rid} users, err := repo.GetCollaborators()
us, err := GetCollaborators(repoName)
if err != nil { if err != nil {
return err return err
} }
iu := &IssueUser{
IssueId: issueID,
RepoId: repo.Id,
}
isNeedAddPoster := true isNeedAddPoster := true
for _, u := range us { for _, u := range users {
iu.Uid = u.Id iu.Uid = u.Id
iu.IsPoster = iu.Uid == pid iu.IsPoster = iu.Uid == posterID
if isNeedAddPoster && iu.IsPoster { if isNeedAddPoster && iu.IsPoster {
isNeedAddPoster = false isNeedAddPoster = false
} }
iu.IsAssigned = iu.Uid == aid iu.IsAssigned = iu.Uid == assigneeID
if _, err = x.Insert(iu); err != nil { if _, err = x.Insert(iu); err != nil {
return err return err
} }
} }
if isNeedAddPoster { if isNeedAddPoster {
iu.Uid = pid iu.Uid = posterID
iu.IsPoster = true iu.IsPoster = true
iu.IsAssigned = iu.Uid == aid iu.IsAssigned = iu.Uid == assigneeID
if _, err = x.Insert(iu); err != nil { if _, err = x.Insert(iu); err != nil {
return err return err
} }

View File

@ -1,12 +1,44 @@
// Copyright 2015 The Gogs 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 migrations package migrations
import ( import (
"errors" "fmt"
"strings"
"time"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/setting"
) )
type migration func(*xorm.Engine) error const _MIN_DB_VER = 0
type Migration interface {
Description() string
Migrate(*xorm.Engine) error
}
type migration struct {
description string
migrate func(*xorm.Engine) error
}
func NewMigration(desc string, fn func(*xorm.Engine) error) Migration {
return &migration{desc, fn}
}
func (m *migration) Description() string {
return m.description
}
func (m *migration) Migrate(x *xorm.Engine) error {
return m.migrate(x)
}
// The version table. Should have only one row with id==1 // The version table. Should have only one row with id==1
type Version struct { type Version struct {
@ -15,30 +47,55 @@ type Version struct {
} }
// This is a sequence of migrations. Add new migrations to the bottom of the list. // This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, replace it with "expiredMigration" // If you want to "retire" a migration, remove it from the top of the list and
var migrations = []migration{} // update _MIN_VER_DB accordingly
var migrations = []Migration{
NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4
}
// Migrate database to current version // Migrate database to current version
func Migrate(x *xorm.Engine) error { func Migrate(x *xorm.Engine) error {
if err := x.Sync(new(Version)); err != nil { if err := x.Sync(new(Version)); err != nil {
return err return fmt.Errorf("sync: %v", err)
} }
currentVersion := &Version{Id: 1} currentVersion := &Version{Id: 1}
has, err := x.Get(currentVersion) has, err := x.Get(currentVersion)
if err != nil { if err != nil {
return err return fmt.Errorf("get: %v", err)
} else if !has { } else if !has {
if _, err = x.InsertOne(currentVersion); err != nil { // If the user table does not exist it is a fresh installation and we
// can skip all migrations.
needsMigration, err := x.IsTableExist("user")
if err != nil {
return err return err
} }
if needsMigration {
isEmpty, err := x.IsTableEmpty("user")
if err != nil {
return err
}
// If the user table is empty it is a fresh installation and we can
// skip all migrations.
needsMigration = !isEmpty
}
if !needsMigration {
currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
}
if _, err = x.InsertOne(currentVersion); err != nil {
return fmt.Errorf("insert: %v", err)
}
} }
v := currentVersion.Version v := currentVersion.Version
for i, m := range migrations[v-_MIN_DB_VER:] {
for i, migration := range migrations[v:] { log.Info("Migration: %s", m.Description())
if err = migration(x); err != nil { if err = m.Migrate(x); err != nil {
return err return fmt.Errorf("do migrate: %v", err)
} }
currentVersion.Version = v + int64(i) + 1 currentVersion.Version = v + int64(i) + 1
if _, err = x.Id(1).Update(currentVersion); err != nil { if _, err = x.Id(1).Update(currentVersion); err != nil {
@ -48,6 +105,267 @@ func Migrate(x *xorm.Engine) error {
return nil return nil
} }
func expiredMigration(x *xorm.Engine) error { func sessionRelease(sess *xorm.Session) {
return errors.New("You are migrating from a too old gogs version") if !sess.IsCommitedOrRollbacked {
sess.Rollback()
}
sess.Close()
}
func accessToCollaboration(x *xorm.Engine) (err error) {
type Collaboration struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
Created time.Time
}
if err = x.Sync(new(Collaboration)); err != nil {
return fmt.Errorf("sync: %v", err)
}
results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
if err != nil {
return err
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
offset := strings.Split(time.Now().String(), " ")[2]
for _, result := range results {
mode := com.StrTo(result["mode"]).MustInt64()
// Collaborators must have write access.
if mode < 2 {
continue
}
userID := com.StrTo(result["uid"]).MustInt64()
repoRefName := string(result["repo"])
var created time.Time
switch {
case setting.UseSQLite3:
created, _ = time.Parse(time.RFC3339, string(result["created"]))
case setting.UseMySQL:
created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
case setting.UsePostgreSQL:
created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
}
// find owner of repository
parts := strings.SplitN(repoRefName, "/", 2)
ownerName := parts[0]
repoName := parts[1]
results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
if err != nil {
return err
}
if len(results) < 1 {
continue
}
ownerID := com.StrTo(results[0]["uid"]).MustInt64()
if ownerID == userID {
continue
}
// test if user is member of owning organization
isMember := false
for _, member := range results {
memberID := com.StrTo(member["memberid"]).MustInt64()
// We can skip all cases that a user is member of the owning organization
if memberID == userID {
isMember = true
}
}
if isMember {
continue
}
results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
if err != nil {
return err
} else if len(results) < 1 {
continue
}
collaboration := &Collaboration{
UserID: userID,
RepoID: com.StrTo(results[0]["id"]).MustInt64(),
}
has, err := sess.Get(collaboration)
if err != nil {
return err
} else if has {
continue
}
collaboration.Created = created
if _, err = sess.InsertOne(collaboration); err != nil {
return err
}
}
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("update owner team table: %v", err)
}
return nil
}
func accessRefactor(x *xorm.Engine) (err error) {
type (
AccessMode int
Access struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
Mode AccessMode
}
UserRepo struct {
UserID int64
RepoID int64
}
)
// 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 fmt.Errorf("select repositories: %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 collaborators: %v", err)
}
for _, user := range results {
userID := com.StrTo(user["user_id"]).MustInt64()
accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
}
if !ownerIsOrganization {
continue
}
// The minimum level to add a new access record,
// because public repository has implicit open access.
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["user_id"]).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 access table: %v", err)
}
// Now we start writing so we make a session
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = sess.Sync2(new(Access)); err != nil {
return fmt.Errorf("sync: %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})
}
if _, err = sess.Insert(accesses); err != nil {
return fmt.Errorf("insert accesses: %v", err)
}
return sess.Commit()
}
func teamToTeamRepo(x *xorm.Engine) error {
type TeamRepo struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
}
teamRepos := make([]*TeamRepo, 0, 50)
results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
if err != nil {
return fmt.Errorf("select teams: %v", err)
}
for _, team := range results {
orgID := com.StrTo(team["org_id"]).MustInt64()
teamID := com.StrTo(team["id"]).MustInt64()
for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
if repoID == 0 {
continue
}
teamRepos = append(teamRepos, &TeamRepo{
OrgID: orgID,
TeamID: teamID,
RepoID: repoID,
})
}
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = sess.Sync2(new(TeamRepo)); err != nil {
return fmt.Errorf("sync: %v", err)
} else if _, err = sess.Insert(teamRepos); err != nil {
return fmt.Errorf("insert team-repos: %v", err)
}
return sess.Commit()
} }

View File

@ -12,10 +12,11 @@ import (
"strings" "strings"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
_ "github.com/lib/pq" _ "github.com/lib/pq"
// "github.com/gogits/gogs/models/migrations" "github.com/gogits/gogs/models/migrations"
"github.com/gogits/gogs/modules/setting" "github.com/gogits/gogs/modules/setting"
) )
@ -23,12 +24,22 @@ import (
type Engine interface { type Engine interface {
Delete(interface{}) (int64, error) Delete(interface{}) (int64, error)
Exec(string, ...interface{}) (sql.Result, error) Exec(string, ...interface{}) (sql.Result, error)
Find(interface{}, ...interface{}) error
Get(interface{}) (bool, error) Get(interface{}) (bool, error)
Insert(...interface{}) (int64, error) Insert(...interface{}) (int64, error)
InsertOne(interface{}) (int64, error)
Id(interface{}) *xorm.Session Id(interface{}) *xorm.Session
Sql(string, ...interface{}) *xorm.Session
Where(string, ...interface{}) *xorm.Session Where(string, ...interface{}) *xorm.Session
} }
func sessionRelease(sess *xorm.Session) {
if !sess.IsCommitedOrRollbacked {
sess.Rollback()
}
sess.Close()
}
var ( var (
x *xorm.Engine x *xorm.Engine
tables []interface{} tables []interface{}
@ -39,24 +50,30 @@ var (
} }
EnableSQLite3 bool EnableSQLite3 bool
UseSQLite3 bool
) )
func init() { func init() {
tables = append(tables, tables = append(tables,
new(User), new(PublicKey), new(Follow), new(Oauth2), new(AccessToken), new(User), new(PublicKey), new(Oauth2), new(AccessToken),
new(Repository), new(Watch), new(Star), new(Action), new(Access), new(Repository), new(Collaboration), new(Access),
new(Watch), new(Star), new(Follow), new(Action),
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone), new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(Mirror), new(Release), new(LoginSource), new(Webhook),
new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser), new(UpdateTask), new(HookTask),
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
new(Notice), new(EmailAddress)) new(Notice), new(EmailAddress))
} }
func LoadModelsConfig() { func LoadModelsConfig() {
sec := setting.Cfg.Section("database") sec := setting.Cfg.Section("database")
DbCfg.Type = sec.Key("DB_TYPE").String() DbCfg.Type = sec.Key("DB_TYPE").String()
if DbCfg.Type == "sqlite3" { switch DbCfg.Type {
UseSQLite3 = true case "sqlite3":
setting.UseSQLite3 = true
case "mysql":
setting.UseMySQL = true
case "postgres":
setting.UsePostgreSQL = true
} }
DbCfg.Host = sec.Key("HOST").String() DbCfg.Host = sec.Key("HOST").String()
DbCfg.Name = sec.Key("NAME").String() DbCfg.Name = sec.Key("NAME").String()
@ -103,6 +120,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
return fmt.Errorf("connect to database: %v", err) return fmt.Errorf("connect to database: %v", err)
} }
x.SetMapper(core.GonicMapper{})
return x.Sync(tables...) return x.Sync(tables...)
} }
@ -112,6 +130,8 @@ func SetEngine() (err error) {
return fmt.Errorf("connect to database: %v", err) return fmt.Errorf("connect to database: %v", err)
} }
x.SetMapper(core.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout, // WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout. // so use log file to instead print to stdout.
logPath := path.Join(setting.LogRootPath, "xorm.log") logPath := path.Join(setting.LogRootPath, "xorm.log")
@ -136,13 +156,14 @@ func NewEngine() (err error) {
return err return err
} }
// if err = migrations.Migrate(x); err != nil { if err = migrations.Migrate(x); err != nil {
// return err return fmt.Errorf("migrate: %v", err)
// } }
if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil { if err = x.StoreEngine("InnoDB").Sync2(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v\n", err) return fmt.Errorf("sync database struct error: %v\n", err)
} }
return nil return nil
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -231,7 +231,7 @@ func (u *User) GetOrganizations() error {
u.Orgs = make([]*User, len(ous)) u.Orgs = make([]*User, len(ous))
for i, ou := range ous { for i, ou := range ous {
u.Orgs[i], err = GetUserById(ou.OrgId) u.Orgs[i], err = GetUserById(ou.OrgID)
if err != nil { if err != nil {
return err return err
} }
@ -398,63 +398,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
return ErrUserNameIllegal return ErrUserNameIllegal
} }
newUserName = strings.ToLower(newUserName) return os.Rename(UserPath(u.LowerName), UserPath(newUserName))
if u.LowerName == newUserName {
// User only change letter cases.
return nil
}
// Update accesses of user.
accesses := make([]Access, 0, 10)
if err = x.Find(&accesses, &Access{UserName: u.LowerName}); err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
for i := range accesses {
accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, u.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, u.LowerName, newUserName, 1)
}
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err
}
}
repos, err := GetRepositories(u.Id, true)
if err != nil {
return err
}
for i := range repos {
accesses = make([]Access, 0, 10)
// Update accesses of user repository.
if err = x.Find(&accesses, &Access{RepoName: u.LowerName + "/" + repos[i].LowerName}); err != nil {
return err
}
for j := range accesses {
// if the access is not the user's access (already updated above)
if accesses[j].UserName != u.LowerName {
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err
}
}
}
}
// Change user directory name.
if err = os.Rename(UserPath(u.LowerName), UserPath(newUserName)); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
} }
// UpdateUser updates user's information. // UpdateUser updates user's information.
@ -527,7 +471,7 @@ func DeleteUser(u *User) error {
return err return err
} }
// Delete all accesses. // Delete all accesses.
if _, err = x.Delete(&Access{UserName: u.LowerName}); err != nil { if _, err = x.Delete(&Access{UserID: u.Id}); err != nil {
return err return err
} }
// Delete all alternative email addresses // Delete all alternative email addresses
@ -570,8 +514,7 @@ func UserPath(userName string) string {
func GetUserByKeyId(keyId int64) (*User, error) { func GetUserByKeyId(keyId int64) (*User, error) {
user := new(User) user := new(User)
rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?" has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
has, err := x.Sql(rawSql, keyId).Get(user)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
@ -580,10 +523,9 @@ func GetUserByKeyId(keyId int64) (*User, error) {
return user, nil return user, nil
} }
// GetUserById returns the user object by given ID if exists. func getUserById(e Engine, id int64) (*User, error) {
func GetUserById(id int64) (*User, error) {
u := new(User) u := new(User)
has, err := x.Id(id).Get(u) has, err := e.Id(id).Get(u)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
@ -592,6 +534,11 @@ func GetUserById(id int64) (*User, error) {
return u, nil return u, nil
} }
// GetUserById returns the user object by given ID if exists.
func GetUserById(id int64) (*User, error) {
return getUserById(x, id)
}
// GetUserByName returns user by given name. // GetUserByName returns user by given name.
func GetUserByName(name string) (*User, error) { func GetUserByName(name string) (*User, error) {
if len(name) == 0 { if len(name) == 0 {
@ -913,7 +860,7 @@ func UpdateMentions(userNames []string, issueId int64) error {
} }
for _, orgUser := range orgUsers { for _, orgUser := range orgUsers {
tempIds = append(tempIds, orgUser.Id) tempIds = append(tempIds, orgUser.ID)
} }
ids = append(ids, tempIds...) ids = append(ids, tempIds...)

View File

@ -38,15 +38,25 @@ type Context struct {
IsSigned bool IsSigned bool
IsBasicAuth bool IsBasicAuth bool
Repo struct { Repo RepoContext
Org struct {
IsOwner bool IsOwner bool
IsTrueOwner bool IsMember bool
IsAdminTeam bool // In owner team or team that has admin permission level.
Organization *models.User
OrgLink string
Team *models.Team
}
}
type RepoContext struct {
AccessMode models.AccessMode
IsWatching bool IsWatching bool
IsBranch bool IsBranch bool
IsTag bool IsTag bool
IsCommit bool IsCommit bool
IsAdmin bool // Current user is admin level.
HasAccess bool
Repository *models.Repository Repository *models.Repository
Owner *models.User Owner *models.User
Commit *git.Commit Commit *git.Commit
@ -60,17 +70,16 @@ type Context struct {
CloneLink models.CloneLink CloneLink models.CloneLink
CommitsCount int CommitsCount int
Mirror *models.Mirror Mirror *models.Mirror
} }
Org struct { // Return if the current user has write access for this repository
IsOwner bool func (r RepoContext) IsOwner() bool {
IsMember bool return r.AccessMode >= models.ACCESS_MODE_WRITE
IsAdminTeam bool // In owner team or team that has admin permission level. }
Organization *models.User
OrgLink string
Team *models.Team // 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. // HasError returns true if error occurs in form validation.

View File

@ -87,7 +87,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
return return
} }
ctx.Data["Team"] = ctx.Org.Team ctx.Data["Team"] = ctx.Org.Team
ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize == models.ORG_ADMIN ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize >= models.ACCESS_MODE_ADMIN
} }
ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam
if requireAdminTeam && !ctx.Org.IsAdminTeam { if requireAdminTeam && !ctx.Org.IsAdminTeam {

View File

@ -5,7 +5,6 @@
package middleware package middleware
import ( import (
"errors"
"fmt" "fmt"
"net/url" "net/url"
"strings" "strings"
@ -29,17 +28,10 @@ func ApiRepoAssignment() macaron.Handler {
err error err error
) )
// Collaborators who have write access can be seen as owners. // Check if the user is the same as the repository owner.
if ctx.IsSigned { if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) u = ctx.User
if err != nil { } else {
ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
return
}
ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
}
if !ctx.Repo.IsTrueOwner {
u, err = models.GetUserByName(userName) u, err = models.GetUserByName(userName)
if err != nil { if err != nil {
if err == models.ErrUserNotExist { if err == models.ErrUserNotExist {
@ -49,67 +41,37 @@ func ApiRepoAssignment() macaron.Handler {
} }
return return
} }
} else {
u = ctx.User
} }
ctx.Repo.Owner = u ctx.Repo.Owner = u
// Organization owner team members are true owners as well.
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
ctx.Repo.IsTrueOwner = true
}
// Get repository. // Get repository.
repo, err := models.GetRepositoryByName(u.Id, repoName) repo, err := models.GetRepositoryByName(u.Id, repoName)
if err != nil { if err != nil {
if err == models.ErrRepoNotExist { if err == models.ErrRepoNotExist {
ctx.Error(404) ctx.Error(404)
return } else {
}
ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL}) ctx.JSON(500, &base.ApiJsonErr{"GetRepositoryByName: " + err.Error(), base.DOC_URL})
}
return return
} else if err = repo.GetOwner(); err != nil { } else if err = repo.GetOwner(); err != nil {
ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL}) ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
return return
} }
// Check if the mirror repository owner(mirror repository doesn't have access). mode, err := models.AccessLevel(ctx.User, repo)
if ctx.IsSigned && !ctx.Repo.IsOwner {
if repo.OwnerId == ctx.User.Id {
ctx.Repo.IsOwner = true
}
// Check if current user has admin permission to repository.
if u.IsOrganization() {
auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
if err != nil { if err != nil {
ctx.JSON(500, &base.ApiJsonErr{"GetHighestAuthorize: " + err.Error(), base.DOC_URL}) ctx.JSON(500, &base.ApiJsonErr{"AccessLevel: " + err.Error(), base.DOC_URL})
return return
} }
if auth == models.ORG_ADMIN {
ctx.Repo.IsOwner = true ctx.Repo.AccessMode = mode
ctx.Repo.IsAdmin = true
}
}
}
// Check access. // Check access.
if repo.IsPrivate && !ctx.Repo.IsOwner { if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
if ctx.User == nil {
ctx.Error(404) ctx.Error(404)
return return
} }
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
if err != nil {
ctx.JSON(500, &base.ApiJsonErr{"HasAccess: " + err.Error(), base.DOC_URL})
return
} else if !hasAccess {
ctx.Error(404)
return
}
}
ctx.Repo.HasAccess = true
ctx.Repo.Repository = repo ctx.Repo.Repository = repo
} }
} }
@ -242,101 +204,49 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
refName = ctx.Params(":path") refName = ctx.Params(":path")
} }
// Collaborators who have write access can be seen as owners. // Check if the user is the same as the repository owner
if ctx.IsSigned { if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.WRITABLE) u = ctx.User
if err != nil { } else {
ctx.Handle(500, "HasAccess", err)
return
}
ctx.Repo.IsTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
}
if !ctx.Repo.IsTrueOwner {
u, err = models.GetUserByName(userName) u, err = models.GetUserByName(userName)
if err != nil { if err != nil {
if err == models.ErrUserNotExist { if err == models.ErrUserNotExist {
ctx.Handle(404, "GetUserByName", err) ctx.Handle(404, "GetUserByName", err)
} else if redirect {
log.Error(4, "GetUserByName", err)
ctx.Redirect(setting.AppSubUrl + "/")
} else { } else {
ctx.Handle(500, "GetUserByName", err) ctx.Handle(500, "GetUserByName", err)
} }
return return
} }
} else {
u = ctx.User
}
if u == nil {
if redirect {
ctx.Redirect(setting.AppSubUrl + "/")
return
}
ctx.Handle(404, "RepoAssignment", errors.New("invliad user account for single repository"))
return
} }
ctx.Repo.Owner = u ctx.Repo.Owner = u
// Organization owner team members are true owners as well.
if ctx.IsSigned && ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOwnedBy(ctx.User.Id) {
ctx.Repo.IsTrueOwner = true
}
// Get repository. // Get repository.
repo, err := models.GetRepositoryByName(u.Id, repoName) repo, err := models.GetRepositoryByName(u.Id, repoName)
if err != nil { if err != nil {
if err == models.ErrRepoNotExist { if err == models.ErrRepoNotExist {
ctx.Handle(404, "GetRepositoryByName", err) ctx.Handle(404, "GetRepositoryByName", err)
return } else {
} else if redirect {
ctx.Redirect(setting.AppSubUrl + "/")
return
}
ctx.Handle(500, "GetRepositoryByName", err) ctx.Handle(500, "GetRepositoryByName", err)
}
return return
} else if err = repo.GetOwner(); err != nil { } else if err = repo.GetOwner(); err != nil {
ctx.Handle(500, "GetOwner", err) ctx.Handle(500, "GetOwner", err)
return return
} }
// Check if the mirror repository owner(mirror repository doesn't have access). mode, err := models.AccessLevel(ctx.User, repo)
if ctx.IsSigned && !ctx.Repo.IsOwner {
if repo.OwnerId == ctx.User.Id {
ctx.Repo.IsOwner = true
}
// Check if current user has admin permission to repository.
if u.IsOrganization() {
auth, err := models.GetHighestAuthorize(u.Id, ctx.User.Id, repo.Id, 0)
if err != nil { if err != nil {
ctx.Handle(500, "GetHighestAuthorize", err) ctx.Handle(500, "AccessLevel", err)
return return
} }
if auth == models.ORG_ADMIN { ctx.Repo.AccessMode = mode
ctx.Repo.IsOwner = true
ctx.Repo.IsAdmin = true
}
}
}
// Check access. // Check access.
if repo.IsPrivate && !ctx.Repo.IsOwner { if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
if ctx.User == nil { ctx.Handle(404, "no access right", err)
ctx.Handle(404, "HasAccess", nil)
return return
} }
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.READABLE)
if err != nil {
ctx.Handle(500, "HasAccess", err)
return
} else if !hasAccess {
ctx.Handle(404, "HasAccess", nil)
return
}
}
ctx.Repo.HasAccess = true
ctx.Data["HasAccess"] = true ctx.Data["HasAccess"] = true
if repo.IsMirror { if repo.IsMirror {
@ -383,8 +293,8 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
ctx.Data["Title"] = u.Name + "/" + repo.Name ctx.Data["Title"] = u.Name + "/" + repo.Name
ctx.Data["Repository"] = repo ctx.Data["Repository"] = repo
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.AccessMode >= models.ACCESS_MODE_WRITE
ctx.Data["IsRepositoryTrueOwner"] = ctx.Repo.IsTrueOwner ctx.Data["IsRepositoryAdmin"] = ctx.Repo.AccessMode >= models.ACCESS_MODE_ADMIN
ctx.Data["DisableSSH"] = setting.DisableSSH ctx.Data["DisableSSH"] = setting.DisableSSH
ctx.Repo.CloneLink, err = repo.CloneLink() ctx.Repo.CloneLink, err = repo.CloneLink()
@ -438,9 +348,9 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
} }
} }
func RequireTrueOwner() macaron.Handler { func RequireAdmin() macaron.Handler {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin { if ctx.Repo.AccessMode < models.ACCESS_MODE_ADMIN {
if !ctx.IsSigned { if !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl) ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl)
ctx.Redirect(setting.AppSubUrl + "/user/login") ctx.Redirect(setting.AppSubUrl + "/user/login")

View File

@ -67,6 +67,11 @@ var (
CookieRememberName string CookieRememberName string
ReverseProxyAuthUser string ReverseProxyAuthUser string
// Database settings.
UseSQLite3 bool
UseMySQL bool
UsePostgreSQL bool
// Webhook settings. // Webhook settings.
Webhook struct { Webhook struct {
TaskInterval int TaskInterval int
@ -267,10 +272,6 @@ func NewConfigContext() {
"StampNano": time.StampNano, "StampNano": time.StampNano,
}[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")] }[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
log.Fatal(4, "Could not create directory %s: %s", AttachmentPath, err)
}
RunUser = Cfg.Section("").Key("RUN_USER").String() RunUser = Cfg.Section("").Key("RUN_USER").String()
curUser := os.Getenv("USER") curUser := os.Getenv("USER")
if len(curUser) == 0 { if len(curUser) == 0 {
@ -293,9 +294,6 @@ func NewConfigContext() {
} else { } else {
RepoRootPath = filepath.Clean(RepoRootPath) RepoRootPath = filepath.Clean(RepoRootPath)
} }
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
log.Fatal(4, "Fail to create repository root path(%s): %v", RepoRootPath, err)
}
ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash") ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
sec = Cfg.Section("picture") sec = Cfg.Section("picture")
@ -304,7 +302,6 @@ func NewConfigContext() {
if !filepath.IsAbs(AvatarUploadPath) { if !filepath.IsAbs(AvatarUploadPath) {
AvatarUploadPath = path.Join(workDir, AvatarUploadPath) AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
} }
os.MkdirAll(AvatarUploadPath, os.ModePerm)
switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") { switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") {
case "duoshuo": case "duoshuo":
GravatarSource = "http://gravatar.duoshuo.com/avatar/" GravatarSource = "http://gravatar.duoshuo.com/avatar/"
@ -369,9 +366,11 @@ func newLogService() {
log.Fatal(4, "Unknown log mode: %s", mode) log.Fatal(4, "Unknown log mode: %s", mode)
} }
validLevels := []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}
// Log level. // Log level.
levelName := Cfg.Section("log."+mode).Key("LEVEL").In("Trace", levelName := Cfg.Section("log."+mode).Key("LEVEL").In(
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"}) Cfg.Section("log").Key("LEVEL").In("Trace", validLevels),
validLevels)
level, ok := logLevels[levelName] level, ok := logLevels[levelName]
if !ok { if !ok {
log.Fatal(4, "Unknown log level: %s", levelName) log.Fatal(4, "Unknown log level: %s", levelName)

View File

@ -238,28 +238,31 @@ func ListMyRepos(ctx *middleware.Context) {
} }
numOwnRepos := len(ownRepos) numOwnRepos := len(ownRepos)
collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name) accessibleRepos, err := ctx.User.GetAccessibleRepositories()
if err != nil { if err != nil {
ctx.JSON(500, &base.ApiJsonErr{"GetCollaborativeRepos: " + err.Error(), base.DOC_URL}) ctx.JSON(500, &base.ApiJsonErr{"GetAccessibleRepositories: " + err.Error(), base.DOC_URL})
return return
} }
repos := make([]*api.Repository, numOwnRepos+len(collaRepos)) repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
for i := range ownRepos { for i := range ownRepos {
repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true}) repos[i] = ToApiRepository(ctx.User, ownRepos[i], api.Permission{true, true, true})
} }
for i := range collaRepos { i := numOwnRepos
if err = collaRepos[i].GetOwner(); err != nil {
for repo, access := range accessibleRepos {
if err = repo.GetOwner(); err != nil {
ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL}) ctx.JSON(500, &base.ApiJsonErr{"GetOwner: " + err.Error(), base.DOC_URL})
return return
} }
j := i + numOwnRepos
repos[j] = ToApiRepository(collaRepos[i].Owner, collaRepos[i].Repository, api.Permission{false, collaRepos[i].CanPush, true}) repos[i] = ToApiRepository(repo.Owner, repo, api.Permission{false, access >= models.ACCESS_MODE_WRITE, true})
// FIXME: cache result to reduce DB query? // FIXME: cache result to reduce DB query?
if collaRepos[i].Owner.IsOrganization() && collaRepos[i].Owner.IsOwnedBy(ctx.User.Id) { if repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(ctx.User.Id) {
repos[j].Permissions.Admin = true repos[i].Permissions.Admin = true
} }
i++
} }
ctx.JSON(200, &repos) ctx.JSON(200, &repos)

View File

@ -12,7 +12,7 @@ import (
) )
func GetRepoRawFile(ctx *middleware.Context) { func GetRepoRawFile(ctx *middleware.Context) {
if ctx.Repo.Repository.IsPrivate && !ctx.Repo.HasAccess { if !ctx.Repo.HasAccess() {
ctx.Error(404) ctx.Error(404)
return return
} }

View File

@ -224,6 +224,7 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
cfg.Section("session").Key("PROVIDER").SetValue("file") cfg.Section("session").Key("PROVIDER").SetValue("file")
cfg.Section("log").Key("MODE").SetValue("file") cfg.Section("log").Key("MODE").SetValue("file")
cfg.Section("log").Key("LEVEL").SetValue("Info")
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true") cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15)) cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15))

View File

@ -165,14 +165,14 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
} }
// Validate permission level. // Validate permission level.
var auth models.AuthorizeType var auth models.AccessMode
switch form.Permission { switch form.Permission {
case "read": case "read":
auth = models.ORG_READABLE auth = models.ACCESS_MODE_READ
case "write": case "write":
auth = models.ORG_WRITABLE auth = models.ACCESS_MODE_WRITE
case "admin": case "admin":
auth = models.ORG_ADMIN auth = models.ACCESS_MODE_ADMIN
default: default:
ctx.Error(401) ctx.Error(401)
return return
@ -181,7 +181,7 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
org := ctx.Org.Organization org := ctx.Org.Organization
t := &models.Team{ t := &models.Team{
OrgId: org.Id, OrgID: org.Id,
Name: form.TeamName, Name: form.TeamName,
Description: form.Description, Description: form.Description,
Authorize: auth, Authorize: auth,
@ -246,14 +246,14 @@ func EditTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
isAuthChanged := false isAuthChanged := false
if !t.IsOwnerTeam() { if !t.IsOwnerTeam() {
// Validate permission level. // Validate permission level.
var auth models.AuthorizeType var auth models.AccessMode
switch form.Permission { switch form.Permission {
case "read": case "read":
auth = models.ORG_READABLE auth = models.ACCESS_MODE_READ
case "write": case "write":
auth = models.ORG_WRITABLE auth = models.ACCESS_MODE_WRITE
case "admin": case "admin":
auth = models.ORG_ADMIN auth = models.ACCESS_MODE_ADMIN
default: default:
ctx.Error(401) ctx.Error(401)
return return

View File

@ -131,18 +131,18 @@ func Http(ctx *middleware.Context) {
} }
if !isPublicPull { if !isPublicPull {
var tp = models.WRITABLE var tp = models.ACCESS_MODE_WRITE
if isPull { if isPull {
tp = models.READABLE tp = models.ACCESS_MODE_READ
} }
has, err := models.HasAccess(authUsername, username+"/"+reponame, tp) has, err := models.HasAccess(authUser, repo, tp)
if err != nil { if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil) ctx.Handle(401, "no basic auth and digit auth", nil)
return return
} else if !has { } else if !has {
if tp == models.READABLE { if tp == models.ACCESS_MODE_READ {
has, err = models.HasAccess(authUsername, username+"/"+reponame, models.WRITABLE) has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE)
if err != nil || !has { if err != nil || !has {
ctx.Handle(401, "no basic auth and digit auth", nil) ctx.Handle(401, "no basic auth and digit auth", nil)
return return
@ -152,6 +152,11 @@ func Http(ctx *middleware.Context) {
return return
} }
} }
if !isPull && repo.IsMirror {
ctx.Handle(401, "can't push to mirror", nil)
return
}
} }
} }

View File

@ -174,7 +174,7 @@ func CreateIssue(ctx *middleware.Context) {
return return
} }
us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) us, err := ctx.Repo.Repository.GetCollaborators()
if err != nil { if err != nil {
ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
return return
@ -218,7 +218,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
return return
} }
_, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) _, err = ctx.Repo.Repository.GetCollaborators()
if err != nil { if err != nil {
send(500, nil, err) send(500, nil, err)
return return
@ -230,7 +230,7 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
} }
// Only collaborators can assign. // Only collaborators can assign.
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
form.AssigneeId = 0 form.AssigneeId = 0
} }
issue := &models.Issue{ issue := &models.Issue{
@ -246,8 +246,8 @@ func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
if err := models.NewIssue(issue); err != nil { if err := models.NewIssue(issue); err != nil {
send(500, nil, err) send(500, nil, err)
return return
} else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id, } else if err := models.NewIssueUserPairs(ctx.Repo.Repository, issue.Id, ctx.Repo.Owner.Id,
ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil { ctx.User.Id, form.AssigneeId); err != nil {
send(500, nil, err) send(500, nil, err)
return return
} }
@ -384,7 +384,7 @@ func ViewIssue(ctx *middleware.Context) {
} }
// Get all collaborators. // Get all collaborators.
ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/")) ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators()
if err != nil { if err != nil {
ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
return return
@ -434,7 +434,7 @@ func ViewIssue(ctx *middleware.Context) {
ctx.Data["Title"] = issue.Name ctx.Data["Title"] = issue.Name
ctx.Data["Issue"] = issue ctx.Data["Issue"] = issue
ctx.Data["Comments"] = comments 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["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = false ctx.Data["IsRepoToolbarIssuesList"] = false
ctx.HTML(200, ISSUE_VIEW) ctx.HTML(200, ISSUE_VIEW)
@ -457,7 +457,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
return return
} }
if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner { if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner() {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -484,7 +484,7 @@ func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
} }
func UpdateIssueLabel(ctx *middleware.Context) { func UpdateIssueLabel(ctx *middleware.Context) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -561,7 +561,7 @@ func UpdateIssueLabel(ctx *middleware.Context) {
} }
func UpdateIssueMilestone(ctx *middleware.Context) { func UpdateIssueMilestone(ctx *middleware.Context) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -607,7 +607,7 @@ func UpdateIssueMilestone(ctx *middleware.Context) {
} }
func UpdateAssignee(ctx *middleware.Context) { func UpdateAssignee(ctx *middleware.Context) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Error(403) ctx.Error(403)
return return
} }
@ -753,7 +753,7 @@ func Comment(ctx *middleware.Context) {
// Check if issue owner changes the status of issue. // Check if issue owner changes the status of issue.
var newStatus string 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") newStatus = ctx.Query("change_status")
} }
if len(newStatus) > 0 { if len(newStatus) > 0 {

View File

@ -41,7 +41,7 @@ func Releases(ctx *middleware.Context) {
tags := make([]*models.Release, len(rawTags)) tags := make([]*models.Release, len(rawTags))
for i, rawTag := range rawTags { for i, rawTag := range rawTags {
for j, rel := range rels { for j, rel := range rels {
if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner) { if rel == nil || (rel.IsDraft && !ctx.Repo.IsOwner()) {
continue continue
} }
if rel.TagName == rawTag { if rel.TagName == rawTag {
@ -140,7 +140,7 @@ func Releases(ctx *middleware.Context) {
} }
func NewRelease(ctx *middleware.Context) { func NewRelease(ctx *middleware.Context) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Handle(403, "release.ReleasesNew", nil) ctx.Handle(403, "release.ReleasesNew", nil)
return return
} }
@ -153,7 +153,7 @@ func NewRelease(ctx *middleware.Context) {
} }
func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Handle(403, "release.ReleasesNew", nil) ctx.Handle(403, "release.ReleasesNew", nil)
return return
} }
@ -211,7 +211,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
} }
func EditRelease(ctx *middleware.Context) { func EditRelease(ctx *middleware.Context) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Handle(403, "release.ReleasesEdit", nil) ctx.Handle(403, "release.ReleasesEdit", nil)
return return
} }
@ -234,7 +234,7 @@ func EditRelease(ctx *middleware.Context) {
} }
func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) { func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) {
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Handle(403, "release.EditReleasePost", nil) ctx.Handle(403, "release.EditReleasePost", nil)
return return
} }

View File

@ -349,7 +349,7 @@ func Action(ctx *middleware.Context) {
case "unstar": case "unstar":
err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false) err = models.StarRepo(ctx.User.Id, ctx.Repo.Repository.Id, false)
case "desc": case "desc":
if !ctx.Repo.IsOwner { if !ctx.Repo.IsOwner() {
ctx.Error(404) ctx.Error(404)
return return
} }

View File

@ -8,7 +8,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"path"
"strings" "strings"
"time" "time"
@ -54,15 +53,11 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) {
newRepoName := form.RepoName newRepoName := form.RepoName
// Check if repository name has been changed. // Check if repository name has been changed.
if ctx.Repo.Repository.Name != newRepoName { if ctx.Repo.Repository.Name != newRepoName {
isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) if models.IsRepositoryExist(ctx.Repo.Owner, newRepoName) {
if err != nil {
ctx.Handle(500, "IsRepositoryExist", err)
return
} else if isExist {
ctx.Data["Err_RepoName"] = true ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil) ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil)
return return
} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil { } else if err := models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
if err == models.ErrRepoNameIllegal { if err == models.ErrRepoNameIllegal {
ctx.Data["Err_RepoName"] = true ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil) ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil)
@ -169,22 +164,12 @@ func SettingsCollaboration(ctx *middleware.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsCollaboration"] = true ctx.Data["PageIsSettingsCollaboration"] = true
repoLink := path.Join(ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName)
if ctx.Req.Method == "POST" { if ctx.Req.Method == "POST" {
name := strings.ToLower(ctx.Query("collaborator")) name := strings.ToLower(ctx.Query("collaborator"))
if len(name) == 0 || ctx.Repo.Owner.LowerName == name { if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path) ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
return return
} }
has, err := models.HasAccess(name, repoLink, models.WRITABLE)
if err != nil {
ctx.Handle(500, "HasAccess", err)
return
} else if has {
ctx.Redirect(setting.AppSubUrl + ctx.Req.URL.Path)
return
}
u, err := models.GetUserByName(name) u, err := models.GetUserByName(name)
if err != nil { if err != nil {
@ -204,9 +189,8 @@ func SettingsCollaboration(ctx *middleware.Context) {
return return
} }
if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink, if err = ctx.Repo.Repository.AddCollaborator(u); err != nil {
Mode: models.WRITABLE}); err != nil { ctx.Handle(500, "AddCollaborator", err)
ctx.Handle(500, "AddAccess", err)
return return
} }
@ -225,50 +209,27 @@ func SettingsCollaboration(ctx *middleware.Context) {
// Delete collaborator. // Delete collaborator.
remove := strings.ToLower(ctx.Query("remove")) remove := strings.ToLower(ctx.Query("remove"))
if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName { if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName {
needDelete := true u, err := models.GetUserByName(remove)
if ctx.User.IsOrganization() {
// Check if user belongs to a team that has access to this repository.
auth, err := models.GetHighestAuthorize(ctx.Repo.Owner.Id, ctx.User.Id, ctx.Repo.Repository.Id, 0)
if err != nil { if err != nil {
ctx.Handle(500, "GetHighestAuthorize", err) ctx.Handle(500, "GetUserByName", err)
return return
} }
if auth > 0 { if err := ctx.Repo.Repository.DeleteCollaborator(u); err != nil {
needDelete = false ctx.Handle(500, "DeleteCollaborator", err)
}
}
if needDelete {
if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
ctx.Handle(500, "DeleteAccess", err)
return return
} }
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
return return
} }
names, err := models.GetCollaboratorNames(repoLink) users, err := ctx.Repo.Repository.GetCollaborators()
if err != nil { if err != nil {
ctx.Handle(500, "GetCollaborators", err) ctx.Handle(500, "GetCollaborators", err)
return return
} }
collaborators := make([]*models.User, 0, len(names)) ctx.Data["Collaborators"] = users
for _, name := range names {
u, err := models.GetUserByName(name)
if err != nil {
ctx.Handle(500, "GetUserByName", err)
return
}
// Does not show organization members.
if ctx.Repo.Owner.IsOrganization() && ctx.Repo.Owner.IsOrgMember(u.Id) {
continue
}
collaborators = append(collaborators, u)
}
ctx.Data["Collaborators"] = collaborators
ctx.HTML(200, COLLABORATION) ctx.HTML(200, COLLABORATION)
} }

View File

@ -49,13 +49,19 @@ func Dashboard(ctx *middleware.Context) {
} else { } else {
// Normal user. // Normal user.
ctxUser = ctx.User ctxUser = ctx.User
collaborates, err := models.GetCollaborativeRepos(ctxUser.Name) collaborates, err := ctx.User.GetAccessibleRepositories()
if err != nil { if err != nil {
ctx.Handle(500, "GetCollaborativeRepos", err) ctx.Handle(500, "GetAccessibleRepositories", err)
return return
} }
ctx.Data["CollaborateCount"] = len(collaborates)
ctx.Data["CollaborativeRepos"] = collaborates repositories := make([]*models.Repository, 0, len(collaborates))
for repo := range collaborates {
repositories = append(repositories, repo)
}
ctx.Data["CollaborateCount"] = len(repositories)
ctx.Data["CollaborativeRepos"] = repositories
} }
ctx.Data["ContextUser"] = ctxUser ctx.Data["ContextUser"] = ctxUser
@ -97,11 +103,15 @@ func Dashboard(ctx *middleware.Context) {
feeds := make([]*models.Action, 0, len(actions)) feeds := make([]*models.Action, 0, len(actions))
for _, act := range actions { for _, act := range actions {
if act.IsPrivate { if act.IsPrivate {
if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, // This prevents having to retrieve the repository for each action
models.READABLE); !has { repo := &models.Repository{Id: act.RepoId, IsPrivate: true}
if act.RepoUserName != ctx.User.LowerName {
if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has {
continue continue
} }
} }
}
// FIXME: cache results? // FIXME: cache results?
u, err := models.GetUserByName(act.ActUserName) u, err := models.GetUserByName(act.ActUserName)
if err != nil { if err != nil {
@ -205,11 +215,15 @@ func Profile(ctx *middleware.Context) {
if !ctx.IsSigned { if !ctx.IsSigned {
continue continue
} }
if has, _ := models.HasAccess(ctx.User.Name, act.RepoUserName+"/"+act.RepoName, // This prevents having to retrieve the repository for each action
models.READABLE); !has { repo := &models.Repository{Id: act.RepoId, IsPrivate: true}
if act.RepoUserName != ctx.User.LowerName {
if has, _ := models.HasAccess(ctx.User, repo, models.ACCESS_MODE_READ); !has {
continue continue
} }
} }
}
// FIXME: cache results? // FIXME: cache results?
u, err := models.GetUserByName(act.ActUserName) u, err := models.GetUserByName(act.ActUserName)
if err != nil { if err != nil {

View File

@ -1 +1 @@
0.5.14.0222 Beta 0.5.16.0228 Beta

View File

@ -1,7 +1,7 @@
</div> </div>
<footer id="footer"> <footer id="footer">
<div class="container clear"> <div class="container clear">
<p class="left" id="footer-rights">© 2015 GoGits · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> · <p class="left" id="footer-rights">© 2015 Gogs · {{.i18n.Tr "version"}}: {{AppVer}} · {{.i18n.Tr "page"}}: <strong>{{LoadTimes .PageStartTime}}</strong> ·
{{.i18n.Tr "template"}}: <strong>{{call .TmplLoadTimes}}</strong></p> {{.i18n.Tr "template"}}: <strong>{{call .TmplLoadTimes}}</strong></p>
<div class="right" id="footer-links"> <div class="right" id="footer-links">

View File

@ -27,7 +27,7 @@
</div> </div>
<div id="org-repo-list"> <div id="org-repo-list">
{{range .Repos}} {{range .Repos}}
{{if or (not .IsPrivate) (.HasAccess $.SignedUser.Name)}} {{if or (not .IsPrivate) (.HasAccess $.SignedUser)}}
<div class="org-repo-item"> <div class="org-repo-item">
<ul class="org-repo-status right"> <ul class="org-repo-status right">
<li><i class="octicon octicon-star"></i> {{.NumStars}}</li> <li><i class="octicon octicon-star"></i> {{.NumStars}}</li>

View File

@ -49,7 +49,7 @@
</a> </a>
</li> </li>
<li id="repo-header-fork"> <li id="repo-header-fork">
<a id="repo-header-fork-btn" {{if or (not $.IsRepositoryTrueOwner) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}> <a id="repo-header-fork-btn" {{if or (not $.IsRepositoryAdmin) $.Owner.IsOrganization}}href="{{AppSubUrl}}/repo/fork?fork_id={{.Id}}"{{end}}>
<button class="btn btn-gray text-bold btn-radius"> <button class="btn btn-gray text-bold btn-radius">
<i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}} <i class="octicon octicon-repo-forked"></i>{{$.i18n.Tr "repo.fork"}}
<span class="num">{{.NumForks}}</span> <span class="num">{{.NumForks}}</span>

View File

@ -20,7 +20,7 @@
<!-- <li> <!-- <li>
<a class="radius" href="#"><i class="octicon octicon-organization"></i>contributors <span class="num right label label-gray label-radius">43</span></a> <a class="radius" href="#"><i class="octicon octicon-organization"></i>contributors <span class="num right label label-gray label-radius">43</span></a>
</li> --> </li> -->
{{if .IsRepositoryTrueOwner}} {{if .IsRepositoryAdmin}}
<li class="border-bottom"></li> <li class="border-bottom"></li>
<li> <li>
<a class="radius" href="{{.RepoLink}}/settings"><i class="octicon octicon-tools"></i>{{.i18n.Tr "repo.settings"}}</a> <a class="radius" href="{{.RepoLink}}/settings"><i class="octicon octicon-tools"></i>{{.i18n.Tr "repo.settings"}}</a>

View File

@ -35,7 +35,7 @@
<li><a href="#">Pulse</a></li> <li><a href="#">Pulse</a></li>
<li><a href="#">Network</a></li> <li><a href="#">Network</a></li>
</ul> </ul>
</li> -->{{end}}{{if .IsRepositoryTrueOwner}} </li> -->{{end}}{{if .IsRepositoryAdmin}}
<li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a> <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a>
</li>{{end}} </li>{{end}}
</ul> </ul>

View File

@ -74,7 +74,7 @@
<div class="tab-pane active"> <div class="tab-pane active">
<div id="org-repo-list"> <div id="org-repo-list">
{{range .Repos}} {{range .Repos}}
{{if or (not .IsPrivate) (.HasAccess $.SignedUserName)}} {{if or (not .IsPrivate) (.HasAccess $.SignedUser)}}
<div class="org-repo-item"> <div class="org-repo-item">
<ul class="org-repo-status right"> <ul class="org-repo-status right">
<li><i class="octicon octicon-star"></i> {{.NumStars}}</li> <li><i class="octicon octicon-star"></i> {{.NumStars}}</li>