aboutsummaryrefslogtreecommitdiff
path: root/models/access.go
diff options
context:
space:
mode:
Diffstat (limited to 'models/access.go')
-rw-r--r--models/access.go214
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)
}