diff --git a/cmd/web.go b/cmd/web.go
index 064a0358f9..0b3b84ab6a 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -48,7 +48,7 @@ and it takes care of all the other things for you`,
Flags: []cli.Flag{},
}
-// checkVersion checks if binary matches the version of temolate files.
+// checkVersion checks if binary matches the version of templates files.
func checkVersion() {
data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/.VERSION"))
if err != nil {
@@ -235,7 +235,7 @@ func runWeb(*cli.Context) {
r.Get("/members/action/:action", org.MembersAction)
r.Get("/teams", org.Teams)
- r.Get("/teams/:team", org.SingleTeam)
+ r.Get("/teams/:team", org.TeamMembers)
r.Get("/teams/:team/action/:action", org.TeamsAction)
}, middleware.OrgAssignment(true, true))
@@ -243,6 +243,8 @@ func runWeb(*cli.Context) {
r.Get("/teams/new", org.NewTeam)
r.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
r.Get("/teams/:team/edit", org.EditTeam)
+ r.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
+ r.Post("/teams/:team/delete", org.DeleteTeam)
m.Group("/settings", func(r *macaron.Router) {
r.Get("", org.Settings)
diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini
index b9e7966c2f..9437bfd197 100644
--- a/conf/locale/locale_en-US.ini
+++ b/conf/locale/locale_en-US.ini
@@ -283,6 +283,13 @@ teams.no_desc = This team has no description
teams.settings = Settings
teams.owners_permission_desc = Owners have full access to all repositories and have admin rights to the organization.
teams.members = Team Members
+teams.update_settings = Update Settings
+teams.delete_team = Delete This Team
+teams.add_team_member = Add Team Member
+teams.delete_team_success = Given team has been successfully deleted.
+teams.read_permission_desc = This team grants Read access: members can view and clone the team's repositories.
+teams.write_permission_desc = This team grants Write access: members can read from and push to the team's repositories.
+teams.admin_permission_desc = This team grants Admin access: members can read from, push to, and add collaborators to the team's repositories.
[action]
create_repo = created repository %s
diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini
index 7e5ac6486e..dbc94f3a0c 100644
--- a/conf/locale/locale_zh-CN.ini
+++ b/conf/locale/locale_zh-CN.ini
@@ -283,6 +283,13 @@ teams.no_desc = 该团队暂无描述
teams.settings = 团队设置
teams.owners_permission_desc = 管理员团队对 所有仓库 具有操作权限,且对组织具有 管理员权限。
teams.members = 团队成员
+teams.update_settings = 更新团队设置
+teams.delete_team = 删除当前团队
+teams.add_team_member = 添加团队成员
+teams.delete_team_success = 指定团队已经被成功删除!
+teams.read_permission_desc = 该团队拥有对所属仓库的 读取 权限,团队成员可以进行查看和克隆等只读操作。
+teams.write_permission_desc = 该团队拥有对所属仓库的 读取 和 写入 的权限。
+teams.admin_permission_desc = 该团队拥有一定的 管理 权限,团队成员可以读取、克隆、推送以及添加其它仓库协作者。
[action]
create_repo = 创建了仓库 %s
diff --git a/gogs.go b/gogs.go
index 1e493b92bc..5c93ae26d5 100644
--- a/gogs.go
+++ b/gogs.go
@@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting"
)
-const APP_VER = "0.4.7.0823 Alpha"
+const APP_VER = "0.4.7.0824 Alpha"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
diff --git a/models/org.go b/models/org.go
index cd4163bab3..27228382d3 100644
--- a/models/org.go
+++ b/models/org.go
@@ -6,11 +6,13 @@ package models
import (
"errors"
+ "fmt"
"os"
"path"
"strings"
"github.com/Unknwon/com"
+ "github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
)
@@ -134,10 +136,10 @@ func CreateOrganization(org, owner *User) (*User, error) {
// Add initial creator to organization and owner team.
ou := &OrgUser{
- Uid: owner.Id,
- OrgId: org.Id,
- IsOwner: true,
- NumTeam: 1,
+ Uid: owner.Id,
+ OrgId: org.Id,
+ IsOwner: true,
+ NumTeams: 1,
}
if _, err = sess.Insert(ou); err != nil {
sess.Rollback()
@@ -199,7 +201,7 @@ type OrgUser struct {
OrgId int64 `xorm:"INDEX UNIQUE(s)"`
IsPublic bool
IsOwner bool
- NumTeam int
+ NumTeams int
}
// IsOrganizationOwner returns true if given user is in the owner team.
@@ -255,17 +257,17 @@ func AddOrgUser(orgId, uid int64) error {
return nil
}
- ou := &OrgUser{
- Uid: uid,
- OrgId: orgId,
- }
-
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
+ ou := &OrgUser{
+ Uid: uid,
+ OrgId: orgId,
+ }
+
if _, err := sess.Insert(ou); err != nil {
sess.Rollback()
return err
@@ -288,12 +290,17 @@ func RemoveOrgUser(orgId, uid int64) error {
return nil
}
+ u, err := GetUserById(uid)
+ if err != nil {
+ return err
+ }
+ org, err := GetUserById(orgId)
+ if err != nil {
+ return err
+ }
+
// Check if the user to delete is the last member in owner team.
if IsOrganizationOwner(orgId, uid) {
- org, err := GetUserById(orgId)
- if err != nil {
- return err
- }
t, err := org.GetOwnerTeam()
if err != nil {
return err
@@ -317,6 +324,33 @@ func RemoveOrgUser(orgId, uid int64) error {
return err
}
+ // Delete all repository accesses.
+ if err = org.GetRepositories(); err != nil {
+ sess.Rollback()
+ return err
+ }
+ access := &Access{
+ UserName: u.LowerName,
+ }
+ for _, repo := range org.Repos {
+ access.RepoName = path.Join(org.LowerName, repo.LowerName)
+ if _, err = sess.Delete(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+
+ // Delete member in his/her teams.
+ ts, err := GetUserTeams(org.Id, u.Id)
+ if err != nil {
+ return err
+ }
+ for _, t := range ts {
+ if err = removeTeamMemberWithSess(org.Id, t.Id, u.Id, sess); err != nil {
+ return err
+ }
+ }
+
return sess.Commit()
}
@@ -352,6 +386,11 @@ type Team struct {
NumMembers int
}
+// IsOwnerTeam returns true if team is owner team.
+func (t *Team) IsOwnerTeam() bool {
+ return t.Name == OWNER_TEAM
+}
+
// IsTeamMember returns true if given user is a member of team.
func (t *Team) IsMember(uid int64) bool {
return IsTeamMember(t.OrgId, t.Id, uid)
@@ -362,7 +401,10 @@ func (t *Team) GetRepositories() error {
idStrs := strings.Split(t.RepoIds, "|")
t.Repos = make([]*Repository, 0, len(idStrs))
for _, str := range idStrs {
- id := com.StrTo(str).MustInt64()
+ if len(str) == 0 {
+ continue
+ }
+ id := com.StrTo(str[1:]).MustInt64()
if id == 0 {
continue
}
@@ -459,15 +501,177 @@ func GetTeamById(teamId int64) (*Team, error) {
return t, nil
}
+// GetHighestAuthorize returns highest repository authorize level for given user and team.
+func GetHighestAuthorize(orgId, uid, teamId, repoId int64) (AuthorizeType, error) {
+ ts, err := GetUserTeams(orgId, uid)
+ if err != nil {
+ return 0, err
+ }
+
+ var auth AuthorizeType = 0
+ for _, t := range ts {
+ // Not current team and has given repository.
+ if t.Id != teamId && strings.Contains(t.RepoIds, "$"+com.ToStr(repoId)+"|") {
+ // Fast return.
+ if t.Authorize == ORG_WRITABLE {
+ return ORG_WRITABLE, nil
+ }
+ if t.Authorize > auth {
+ auth = t.Authorize
+ }
+ }
+ }
+ return auth, nil
+}
+
// UpdateTeam updates information of team.
-func UpdateTeam(t *Team) error {
+func UpdateTeam(t *Team, authChanged bool) (err error) {
+ if !IsLegalName(t.Name) {
+ return ErrTeamNameIllegal
+ }
+
if len(t.Description) > 255 {
t.Description = t.Description[:255]
}
+ sess := x.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ // Update access for team members if needed.
+ if authChanged && !t.IsOwnerTeam() {
+ if err = t.GetRepositories(); err != nil {
+ return err
+ } else if err = t.GetMembers(); err != nil {
+ return err
+ }
+
+ // Get organization.
+ org, err := GetUserById(t.OrgId)
+ if err != nil {
+ return err
+ }
+
+ mode := READABLE
+ if t.Authorize > ORG_READABLE {
+ mode = WRITABLE
+ }
+ access := &Access{
+ Mode: mode,
+ }
+
+ for _, repo := range t.Repos {
+ access.RepoName = path.Join(org.LowerName, repo.LowerName)
+ for _, u := range t.Members {
+ // ORG_WRITABLE is the highest authorize level for now.
+ // Skip checking others if current team has this level.
+ if t.Authorize < ORG_WRITABLE {
+ auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id)
+ if err != nil {
+ sess.Rollback()
+ return err
+ }
+ if auth >= t.Authorize {
+ continue // Other team has higher or same authorize level.
+ }
+ }
+
+ access.UserName = u.LowerName
+ if _, err = sess.Update(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+ }
+ }
+
t.LowerName = strings.ToLower(t.Name)
- _, err := x.Id(t.Id).AllCols().Update(t)
- return err
+ if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return sess.Commit()
+}
+
+// DeleteTeam deletes given team.
+// It's caller's responsibility to assign organization ID.
+func DeleteTeam(t *Team) error {
+ if err := t.GetRepositories(); err != nil {
+ return err
+ } else if err = t.GetMembers(); err != nil {
+ return err
+ }
+
+ // Get organization.
+ org, err := GetUserById(t.OrgId)
+ if err != nil {
+ return err
+ }
+
+ sess := x.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ // Delete all accesses.
+ mode := READABLE
+ if t.Authorize > ORG_READABLE {
+ mode = WRITABLE
+ }
+ access := new(Access)
+
+ for _, repo := range t.Repos {
+ access.RepoName = path.Join(org.LowerName, repo.LowerName)
+ for _, u := range t.Members {
+ access.UserName = u.LowerName
+ access.Mode = mode
+ auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id)
+ if err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ if auth == 0 {
+ if _, err = sess.Delete(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ } else if auth < t.Authorize {
+ // Downgrade authorize level.
+ mode := READABLE
+ if auth > ORG_READABLE {
+ mode = WRITABLE
+ }
+ access.Mode = mode
+ if _, err = sess.Update(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+ }
+ }
+
+ // Delete team-user.
+ if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.Id).Delete(new(TeamUser)); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ // Delete team.
+ if _, err = sess.Id(t.Id).Delete(new(Team)); err != nil {
+ sess.Rollback()
+ return err
+ }
+ // Update organization number of teams.
+ if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams - 1 WHERE id = ?", t.OrgId); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ return sess.Commit()
}
// ___________ ____ ___
@@ -509,12 +713,37 @@ func GetTeamMembers(orgId, teamId int64) ([]*User, error) {
return us, nil
}
+// GetUserTeams returns all teams that user belongs to in given origanization.
+func GetUserTeams(orgId, uid int64) ([]*Team, error) {
+ tus := make([]*TeamUser, 0, 5)
+ if err := x.Where("uid=?", uid).And("org_id=?", orgId).Find(&tus); err != nil {
+ return nil, err
+ }
+
+ ts := make([]*Team, len(tus))
+ for i, tu := range tus {
+ t := new(Team)
+ has, err := x.Id(tu.TeamId).Get(t)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrTeamNotExist
+ }
+ ts[i] = t
+ }
+ return ts, nil
+}
+
// AddTeamMember adds new member to given team of given organization.
func AddTeamMember(orgId, teamId, uid int64) error {
- if !IsOrganizationMember(orgId, uid) || IsTeamMember(orgId, teamId, uid) {
+ if IsTeamMember(orgId, teamId, uid) {
return nil
}
+ if err := AddOrgUser(orgId, uid); err != nil {
+ return err
+ }
+
// Get team and its repositories.
t, err := GetTeamById(teamId)
if err != nil {
@@ -569,18 +798,49 @@ func AddTeamMember(orgId, teamId, uid int64) error {
// Give access to team repositories.
for _, repo := range t.Repos {
- access.RepoName = path.Join(org.LowerName, repo.LowerName)
- if _, err = sess.Insert(access); err != nil {
+ auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id)
+ if err != nil {
sess.Rollback()
return err
}
+
+ access.Id = 0
+ access.RepoName = path.Join(org.LowerName, repo.LowerName)
+ // Equal 0 means given access doesn't exist.
+ if auth == 0 {
+ if _, err = sess.Insert(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ } else if auth < t.Authorize {
+ if _, err = sess.Update(access); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+ }
+ fmt.Println("kao")
+
+ // We make sure it exists before.
+ ou := new(OrgUser)
+ _, err = sess.Where("uid=?", uid).And("org_id=?", orgId).Get(ou)
+ if err != nil {
+ sess.Rollback()
+ return err
+ }
+ ou.NumTeams++
+ if t.IsOwnerTeam() {
+ ou.IsOwner = true
+ }
+ if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil {
+ sess.Rollback()
+ return err
}
return sess.Commit()
}
-// RemoveTeamMember removes member from given team of given organization.
-func RemoveTeamMember(orgId, teamId, uid int64) error {
+func removeTeamMemberWithSess(orgId, teamId, uid int64, sess *xorm.Session) error {
if !IsTeamMember(orgId, teamId, uid) {
return nil
}
@@ -590,6 +850,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error {
if err != nil {
return err
}
+
+ // Check if the user to delete is the last member in owner team.
+ if t.IsOwnerTeam() && t.NumMembers == 1 {
+ return ErrLastOrgOwner
+ }
+
t.NumMembers--
if err = t.GetRepositories(); err != nil {
@@ -608,22 +874,12 @@ func RemoveTeamMember(orgId, teamId, uid int64) error {
return err
}
- sess := x.NewSession()
- defer sess.Close()
- if err := sess.Begin(); err != nil {
- return err
- }
-
tu := &TeamUser{
Uid: uid,
OrgId: orgId,
TeamId: teamId,
}
- access := &Access{
- UserName: u.LowerName,
- }
-
if _, err := sess.Delete(tu); err != nil {
sess.Rollback()
return err
@@ -633,13 +889,63 @@ func RemoveTeamMember(orgId, teamId, uid int64) error {
}
// Delete access to team repositories.
+ access := &Access{
+ UserName: u.LowerName,
+ }
+
for _, repo := range t.Repos {
- access.RepoName = path.Join(org.LowerName, repo.LowerName)
- if _, err = sess.Delete(access); err != nil {
+ auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id)
+ if err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ // Delete access if this is the last team user belongs to.
+ if auth == 0 {
+ access.RepoName = path.Join(org.LowerName, repo.LowerName)
+ _, err = sess.Delete(access)
+ } else if auth < t.Authorize {
+ // Downgrade authorize level.
+ mode := READABLE
+ if auth > ORG_READABLE {
+ mode = WRITABLE
+ }
+ access.Mode = mode
+ _, err = sess.Update(access)
+ }
+ if err != nil {
sess.Rollback()
return err
}
}
+ // This must exist.
+ ou := new(OrgUser)
+ _, err = sess.Where("uid=?", uid).And("org_id=?", org.Id).Get(ou)
+ if err != nil {
+ sess.Rollback()
+ return err
+ }
+ ou.NumTeams--
+ if t.IsOwnerTeam() {
+ ou.IsOwner = false
+ }
+ if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return nil
+}
+
+// RemoveTeamMember removes member from given team of given organization.
+func RemoveTeamMember(orgId, teamId, uid int64) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ if err := removeTeamMemberWithSess(orgId, teamId, uid, sess); err != nil {
+ return err
+ }
return sess.Commit()
}
diff --git a/models/repo.go b/models/repo.go
index 9666192df6..f5d1ca834e 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -525,6 +525,7 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
return nil, err
}
for _, u := range us {
+ access.Id = 0
access.UserName = u.LowerName
if _, err = sess.Insert(access); err != nil {
sess.Rollback()
@@ -707,6 +708,10 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) {
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
+ if !IsLegalName(newRepoName) {
+ return ErrRepoNameIllegal
+ }
+
// Update accesses.
accesses := make([]Access, 0, 10)
if err = x.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
diff --git a/models/user.go b/models/user.go
index 757c290b9c..e8db1ad157 100644
--- a/models/user.go
+++ b/models/user.go
@@ -54,7 +54,8 @@ type User struct {
LoginSource int64 `xorm:"not null default 0"`
LoginName string
Type UserType
- Orgs []*User `xorm:"-"`
+ Orgs []*User `xorm:"-"`
+ Repos []*Repository `xorm:"-"`
NumFollowers int
NumFollowings int
NumStars int
@@ -143,6 +144,12 @@ func (u *User) GetOrganizationCount() (int64, error) {
return x.Where("uid=?", u.Id).Count(new(OrgUser))
}
+// GetRepositories returns all repositories that user owns, including private repositories.
+func (u *User) GetRepositories() (err error) {
+ u.Repos, err = GetRepositories(u.Id, true)
+ return err
+}
+
// GetOrganizations returns all organizations that user belongs to.
func (u *User) GetOrganizations() error {
ous, err := GetOrgUsersByUserId(u.Id)
diff --git a/modules/middleware/context.go b/modules/middleware/context.go
index 80975e999e..3ef1b1d620 100644
--- a/modules/middleware/context.go
+++ b/modules/middleware/context.go
@@ -46,6 +46,7 @@ type Context struct {
IsBranch bool
IsTag bool
IsCommit bool
+ IsAdmin bool // Current user is admin level.
HasAccess bool
Repository *models.Repository
Owner *models.User
diff --git a/modules/middleware/org.go b/modules/middleware/org.go
index c85221a5ab..ee4460b274 100644
--- a/modules/middleware/org.go
+++ b/modules/middleware/org.go
@@ -8,6 +8,7 @@ import (
"github.com/Unknwon/macaron"
"github.com/gogits/gogs/models"
+ "github.com/gogits/gogs/modules/log"
)
func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
@@ -35,6 +36,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
if err == models.ErrUserNotExist {
ctx.Handle(404, "GetUserByName", err)
} else if redirect {
+ log.Error(4, "GetUserByName", err)
ctx.Redirect("/")
} else {
ctx.Handle(500, "GetUserByName", err)
@@ -52,17 +54,14 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
} else {
if org.IsOrgMember(ctx.User.Id) {
ctx.Org.IsMember = true
- // TODO: ctx.Org.IsAdminTeam
}
}
}
if (requireMember && !ctx.Org.IsMember) ||
- (requireOwner && !ctx.Org.IsOwner) ||
- (requireAdminTeam && !ctx.Org.IsAdminTeam) {
+ (requireOwner && !ctx.Org.IsOwner) {
ctx.Handle(404, "OrgAssignment", err)
return
}
- ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
ctx.Org.OrgLink = "/org/" + org.Name
@@ -76,6 +75,7 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
if err == models.ErrTeamNotExist {
ctx.Handle(404, "GetTeam", err)
} else if redirect {
+ log.Error(4, "GetTeam", err)
ctx.Redirect("/")
} else {
ctx.Handle(500, "GetTeam", err)
@@ -83,6 +83,12 @@ func OrgAssignment(redirect bool, args ...bool) macaron.Handler {
return
}
ctx.Data["Team"] = ctx.Org.Team
+ ctx.Org.IsAdminTeam = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize == models.ORG_ADMIN
+ }
+ ctx.Data["IsAdminTeam"] = ctx.Org.IsAdminTeam
+ if requireAdminTeam && !ctx.Org.IsAdminTeam {
+ ctx.Handle(404, "OrgAssignment", err)
+ return
}
}
}
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index 3db1932af0..68a9a2d7fb 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -59,6 +59,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
if err == models.ErrUserNotExist {
ctx.Handle(404, "GetUserByName", err)
} else if redirect {
+ log.Error(4, "GetUserByName", err)
ctx.Redirect("/")
} else {
ctx.Handle(500, "GetUserByName", err)
@@ -84,7 +85,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
ctx.Repo.IsTrueOwner = true
}
- // get repository
+ // Get repository.
repo, err := models.GetRepositoryByName(u.Id, repoName)
if err != nil {
if err == models.ErrRepoNotExist {
@@ -102,8 +103,22 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
}
// Check if the mirror repository owner(mirror repository doesn't have access).
- if ctx.IsSigned && !ctx.Repo.IsOwner && repo.OwnerId == ctx.User.Id {
- ctx.Repo.IsOwner = true
+ 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, 0, repo.Id)
+ if err != nil {
+ ctx.Handle(500, "GetHighestAuthorize", err)
+ return
+ }
+ if auth == models.ORG_ADMIN {
+ ctx.Repo.IsOwner = true
+ ctx.Repo.IsAdmin = true
+ }
+ }
}
// Check access.
@@ -281,7 +296,7 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler {
func RequireTrueOwner() macaron.Handler {
return func(ctx *Context) {
- if !ctx.Repo.IsTrueOwner {
+ if !ctx.Repo.IsTrueOwner && !ctx.Repo.IsAdmin {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
ctx.Redirect("/user/login")
diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css
index 9aa7206bdf..48468c7e80 100644
--- a/public/ng/css/gogs.css
+++ b/public/ng/css/gogs.css
@@ -1298,27 +1298,33 @@ The register and sign-in page style
.repo-setting-zone {
padding: 30px;
}
+#team-members-list,
#repo-collab-list {
list-style: none;
padding: 10px 0 5px 0;
}
+#team-members-list li.collab,
#repo-collab-list li.collab {
clear: both;
height: 50px;
padding: 0 15px 0 15px;
}
+#team-members-list a.member,
#repo-collab-list a.member {
color: #444;
height: 50px;
line-height: 50px;
}
+#team-members-list a.member:hover,
#repo-collab-list a.member:hover {
color: #4183C4;
}
+#team-members-list .avatar,
#repo-collab-list .avatar {
margin-right: 1em;
width: 40px;
}
+#team-members-list .remove-collab,
#repo-collab-list .remove-collab {
color: #DD4B39;
}
@@ -1871,3 +1877,14 @@ textarea#issue-add-content {
#org-team-card .panel-footer {
padding: 10px 20px;
}
+#team-members-list .panel-body .search {
+ padding: 4px 0 10px 10px;
+ border-bottom: 1px solid #dddddd;
+}
+#team-members-list li.collab {
+ padding-top: 10px !important;
+ border-bottom: 1px solid #dddddd;
+}
+#team-members-list li.collab:last-child {
+ border-bottom: 0;
+}
diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js
index 5e6a6a6b91..52000f3664 100644
--- a/public/ng/js/gogs.js
+++ b/public/ng/js/gogs.js
@@ -351,6 +351,41 @@ function initInvite() {
});
}
+function initOrgTeamCreate() {
+ // Delete team.
+ $('#org-team-delete').click(function (e) {
+ if (!confirm('This team is going to be deleted, do you want to continue?')) {
+ e.preventDefault();
+ return true;
+ }
+ var $form = $('#team-create-form')
+ $form.attr('action', $form.data('delete-url'));
+ });
+}
+
+function initTeamMembersList() {
+ // Add team member.
+ var $ul = $('#org-team-members-list');
+ $('#org-team-members-add').on('keyup', function () {
+ var $this = $(this);
+ if (!$this.val()) {
+ $ul.toggleHide();
+ return;
+ }
+ Gogs.searchUsers($this.val(), $ul);
+ }).on('focus', function () {
+ if (!$(this).val()) {
+ $ul.toggleHide();
+ } else {
+ $ul.toggleShow();
+ }
+ }).next().next().find('ul').on("click", 'li', function () {
+ $('#org-team-members-add').val($(this).text());
+ $ul.toggleHide();
+ });
+
+}
+
$(document).ready(function () {
initCore();
if ($('#user-profile-setting').length) {
@@ -368,6 +403,12 @@ $(document).ready(function () {
if ($('#invite-box').length) {
initInvite();
}
+ if ($('#team-create-form').length) {
+ initOrgTeamCreate();
+ }
+ if ($('#team-members-list').length) {
+ initTeamMembersList();
+ }
Tabs('#dashboard-sidebar-menu');
diff --git a/public/ng/less/gogs/organization.less b/public/ng/less/gogs/organization.less
index 58039d90b3..a62dcbb3e0 100644
--- a/public/ng/less/gogs/organization.less
+++ b/public/ng/less/gogs/organization.less
@@ -196,4 +196,19 @@
.panel-footer {
padding: 10px 20px;
}
+}
+#team-members-list {
+ .panel-body .search {
+ padding: 4px 0 10px 10px;
+ border-bottom: 1px solid #dddddd;
+ }
+}
+#team-members-list {
+ li.collab {
+ padding-top: 10px !important;
+ border-bottom: 1px solid #dddddd;
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
}
\ No newline at end of file
diff --git a/public/ng/less/gogs/repository.less b/public/ng/less/gogs/repository.less
index 8f9a97fa02..2f97289852 100644
--- a/public/ng/less/gogs/repository.less
+++ b/public/ng/less/gogs/repository.less
@@ -426,6 +426,7 @@ border-top-right-radius: .25em;
.repo-setting-zone {
padding: 30px;
}
+#team-members-list,
#repo-collab-list {
list-style: none;
padding: 10px 0 5px 0;
diff --git a/routers/org/members.go b/routers/org/members.go
index 1e249e8be2..823daec948 100644
--- a/routers/org/members.go
+++ b/routers/org/members.go
@@ -82,7 +82,12 @@ func MembersAction(ctx *middleware.Context) {
})
return
}
- ctx.Redirect(ctx.Org.OrgLink + "/members")
+
+ if ctx.Params(":action") != "leave" {
+ ctx.Redirect(ctx.Org.OrgLink + "/members")
+ } else {
+ ctx.Redirect("/")
+ }
}
func Invitation(ctx *middleware.Context) {
diff --git a/routers/org/teams.go b/routers/org/teams.go
index 8eb86c49fd..4c986d4aa0 100644
--- a/routers/org/teams.go
+++ b/routers/org/teams.go
@@ -5,6 +5,8 @@
package org
import (
+ "github.com/Unknwon/com"
+
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
@@ -39,23 +41,71 @@ func Teams(ctx *middleware.Context) {
}
func TeamsAction(ctx *middleware.Context) {
+ uid := com.StrTo(ctx.Query("uid")).MustInt64()
+ if uid == 0 {
+ ctx.Redirect(ctx.Org.OrgLink + "/teams")
+ return
+ }
+
+ page := ctx.Query("page")
var err error
switch ctx.Params(":action") {
case "join":
+ if !ctx.Org.IsOwner {
+ ctx.Error(404)
+ return
+ }
err = ctx.Org.Team.AddMember(ctx.User.Id)
case "leave":
err = ctx.Org.Team.RemoveMember(ctx.User.Id)
+ case "remove":
+ if !ctx.Org.IsOwner {
+ ctx.Error(404)
+ return
+ }
+ err = ctx.Org.Team.RemoveMember(uid)
+ page = "team"
+ case "add":
+ if !ctx.Org.IsOwner {
+ ctx.Error(404)
+ return
+ }
+ uname := ctx.Query("uname")
+ var u *models.User
+ u, err = models.GetUserByName(uname)
+ if err != nil {
+ if err == models.ErrUserNotExist {
+ ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
+ ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName)
+ } else {
+ ctx.Handle(500, " GetUserByName", err)
+ }
+ return
+ }
+
+ err = ctx.Org.Team.AddMember(u.Id)
+ page = "team"
}
if err != nil {
- log.Error(4, "Action(%s): %v", ctx.Params(":action"), err)
- ctx.JSON(200, map[string]interface{}{
- "ok": false,
- "err": err.Error(),
- })
- return
+ if err == models.ErrLastOrgOwner {
+ ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
+ } else {
+ log.Error(4, "Action(%s): %v", ctx.Params(":action"), err)
+ ctx.JSON(200, map[string]interface{}{
+ "ok": false,
+ "err": err.Error(),
+ })
+ return
+ }
+ }
+
+ switch page {
+ case "team":
+ ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName)
+ default:
+ ctx.Redirect(ctx.Org.OrgLink + "/teams")
}
- ctx.Redirect(ctx.Org.OrgLink + "/teams")
}
func NewTeam(ctx *middleware.Context) {
@@ -116,13 +166,76 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName)
}
-func EditTeam(ctx *middleware.Context) {
- ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Edit Team"
- ctx.HTML(200, "org/edit_team")
-}
-
-func SingleTeam(ctx *middleware.Context) {
+func TeamMembers(ctx *middleware.Context) {
ctx.Data["Title"] = ctx.Org.Team.Name
ctx.Data["PageIsOrgTeams"] = true
+ if err := ctx.Org.Team.GetMembers(); err != nil {
+ ctx.Handle(500, "GetMembers", err)
+ return
+ }
ctx.HTML(200, TEAM_MEMBERS)
}
+
+func EditTeam(ctx *middleware.Context) {
+ ctx.Data["Title"] = ctx.Org.Organization.FullName
+ ctx.Data["PageIsOrgTeams"] = true
+ ctx.Data["team_name"] = ctx.Org.Team.Name
+ ctx.Data["desc"] = ctx.Org.Team.Description
+ ctx.HTML(200, TEAM_NEW)
+}
+
+func EditTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
+ t := ctx.Org.Team
+ ctx.Data["Title"] = ctx.Org.Organization.FullName
+ ctx.Data["PageIsOrgTeams"] = true
+ ctx.Data["team_name"] = t.Name
+ ctx.Data["desc"] = t.Description
+
+ if ctx.HasError() {
+ ctx.HTML(200, TEAM_NEW)
+ return
+ }
+
+ isAuthChanged := false
+ if !t.IsOwnerTeam() {
+ // Validate permission level.
+ var auth models.AuthorizeType
+ switch form.Permission {
+ case "read":
+ auth = models.ORG_READABLE
+ case "write":
+ auth = models.ORG_WRITABLE
+ case "admin":
+ auth = models.ORG_ADMIN
+ default:
+ ctx.Error(401)
+ return
+ }
+
+ t.Name = form.TeamName
+ if t.Authorize != auth {
+ isAuthChanged = true
+ t.Authorize = auth
+ }
+ }
+ t.Description = form.Description
+ if err := models.UpdateTeam(t, isAuthChanged); err != nil {
+ if err == models.ErrTeamNameIllegal {
+ ctx.Data["Err_TeamName"] = true
+ ctx.RenderWithErr(ctx.Tr("form.illegal_team_name"), TEAM_NEW, &form)
+ } else {
+ ctx.Handle(500, "UpdateTeam", err)
+ }
+ return
+ }
+ ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName)
+}
+
+func DeleteTeam(ctx *middleware.Context) {
+ if err := models.DeleteTeam(ctx.Org.Team); err != nil {
+ ctx.Handle(500, "DeleteTeam", err)
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
+ ctx.Redirect(ctx.Org.OrgLink + "/teams")
+}
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index 10a6f72db7..866c0cd936 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -56,7 +56,12 @@ func SettingsPost(ctx *middleware.Context, form auth.RepoSettingForm) {
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), SETTINGS_OPTIONS, nil)
return
} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
- ctx.Handle(500, "ChangeRepositoryName", err)
+ if err == models.ErrRepoNameIllegal {
+ ctx.Data["Err_RepoName"] = true
+ ctx.RenderWithErr(ctx.Tr("form.illegal_repo_name"), SETTINGS_OPTIONS, nil)
+ } else {
+ ctx.Handle(500, "ChangeRepositoryName", err)
+ }
return
}
log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName)
@@ -185,9 +190,24 @@ func SettingsCollaboration(ctx *middleware.Context) {
// Delete collaborator.
remove := strings.ToLower(ctx.Query("remove"))
if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName {
- if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
- ctx.Handle(500, "DeleteAccess", err)
- return
+ needDelete := true
+ 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, 0, ctx.Repo.Repository.Id)
+ if err != nil {
+ ctx.Handle(500, "GetHighestAuthorize", err)
+ return
+ }
+ if auth > 0 {
+ needDelete = false
+ }
+ }
+
+ if needDelete {
+ if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
+ ctx.Handle(500, "DeleteAccess", err)
+ return
+ }
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
diff --git a/templates/.VERSION b/templates/.VERSION
index f152becda3..3e68f858b5 100644
--- a/templates/.VERSION
+++ b/templates/.VERSION
@@ -1 +1 @@
-0.4.7.0823 Alpha
\ No newline at end of file
+0.4.7.0824 Alpha
\ No newline at end of file
diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl
index afd695ec7d..6f3078de50 100644
--- a/templates/org/home.tmpl
+++ b/templates/org/home.tmpl
@@ -18,6 +18,7 @@
+ {{$isMember := .Org.IsOrgMember $.SignedUser.Id}}
{{if .IsOrganizationOwner}}
@@ -26,6 +27,7 @@
{{range .Repos}}
+ {{if or $isMember (not .IsPrivate)}}
+ {{end}}
{{end}}
@@ -42,12 +45,16 @@
+ {{if $isMember}}
+ {{end}}
diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl
index 3ab92bbfc0..1c530982fb 100644
--- a/templates/org/member/members.tmpl
+++ b/templates/org/member/members.tmpl
@@ -6,7 +6,7 @@
{{template "ng/base/alert" .}}
diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index 8faeb9ef31..d3176be152 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -2,7 +2,8 @@
{{template "ng/base/header" .}}
{{template "org/base/header" .}}
-
+
+ {{template "ng/base/alert" .}}
{{template "org/team/sidebar" .}}
diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl
index f02512e052..ce8c15236b 100644
--- a/templates/org/team/new.tmpl
+++ b/templates/org/team/new.tmpl
@@ -2,16 +2,21 @@
{{template "ng/base/header" .}}
{{template "org/base/header" .}}
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index 1ab24e9aac..b760e002fc 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -1,9 +1,9 @@
@@ -11,16 +11,24 @@
{{if .Team.Description}}{{.Team.Description}}{{else}}{{.i18n.Tr "org.teams.no_desc"}}{{end}}
{{if eq .Team.LowerName "owners"}}
{{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}}
+ {{else if (eq .Team.Authorize 1)}}
+ {{.i18n.Tr "org.teams.read_permission_desc" | Str2html}}
+ {{else if (eq .Team.Authorize 2)}}
+ {{.i18n.Tr "org.teams.write_permission_desc" | Str2html}}
+ {{else if (eq .Team.Authorize 3)}}
+ {{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
{{end}}
+ {{if .IsOrganizationOwner}}
+ {{end}}
\ No newline at end of file
diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl
index 3e0846d652..9c47cb5a41 100644
--- a/templates/org/team/teams.tmpl
+++ b/templates/org/team/teams.tmpl
@@ -6,7 +6,7 @@
{{template "ng/base/alert" .}}
@@ -16,9 +16,9 @@