diff options
Diffstat (limited to 'models/access.go')
-rw-r--r-- | models/access.go | 214 |
1 files changed, 164 insertions, 50 deletions
diff --git a/models/access.go b/models/access.go index 81aa43dc..f353c39a 100644 --- a/models/access.go +++ b/models/access.go @@ -5,76 +5,190 @@ package models import ( - "strings" - "time" - - "github.com/go-xorm/xorm" + "fmt" ) -type AccessType int +type AccessMode int const ( - READABLE AccessType = iota + 1 - WRITABLE + ACCESS_MODE_NONE AccessMode = iota + 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 { - Id int64 - UserName string `xorm:"UNIQUE(s)"` - RepoName string `xorm:"UNIQUE(s)"` // <user name>/<repo name> - Mode AccessType `xorm:"UNIQUE(s)"` - Created time.Time `xorm:"CREATED"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"UNIQUE(s)"` + RepoID int64 `xorm:"UNIQUE(s)"` + Mode AccessMode +} + +func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) { + mode := ACCESS_MODE_NONE + if !repo.IsPrivate { + mode = ACCESS_MODE_READ + } + + 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 } -// AddAccess adds new access record. -func AddAccess(access *Access) error { - access.UserName = strings.ToLower(access.UserName) - access.RepoName = strings.ToLower(access.RepoName) - _, err := x.Insert(access) - return err +// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the +// user does not have access. User can be nil! +func AccessLevel(u *User, repo *Repository) (AccessMode, error) { + return accessLevel(x, u, repo) } -// UpdateAccess updates access information. -func UpdateAccess(access *Access) error { - access.UserName = strings.ToLower(access.UserName) - access.RepoName = strings.ToLower(access.RepoName) - _, err := x.Id(access.Id).Update(access) - return err +func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) { + mode, err := accessLevel(e, u, repo) + return testMode <= mode, err } -// DeleteAccess deletes access record. -func DeleteAccess(access *Access) error { - _, err := x.Delete(access) - return err +// HasAccess returns true if someone has the request access level. User can be nil! +func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) { + return hasAccess(x, u, repo, testMode) } -// UpdateAccess updates access information with session for rolling back. -func UpdateAccessWithSession(sess *xorm.Session, access *Access) error { - if _, err := sess.Id(access.Id).Update(access); err != nil { - 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 } - return nil + + 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 } -// HasAccess returns true if someone can read or write to given repository. -// The repoName should be in format <username>/<reponame>. -func HasAccess(uname, repoName string, mode AccessType) (bool, error) { - if len(repoName) == 0 { - return false, nil +// 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 } - access := &Access{ - UserName: strings.ToLower(uname), - RepoName: strings.ToLower(repoName), + + 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, + }) } - has, err := x.Get(access) + + // 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 +} + +// FIXME: should be able to have read-only access. +// Give all collaborators write access. +func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error { + collaborators, err := repo.getCollaborators(e) if err != nil { - return false, err - } else if !has { - return false, nil - } else if mode > access.Mode { - return false, nil + return fmt.Errorf("getCollaborators: %v", err) + } + 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 true, nil + return repo.refreshAccesses(e, accessMap) +} + +// RecalculateAccesses recalculates all accesses for repository. +func (r *Repository) RecalculateAccesses() error { + return r.recalculateAccesses(x) } |