Team permission to create repository in organization (#8312)
* Add team permission setting to allow creating repo in organization. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add test case for creating repo when have team creation access. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * build error: should omit comparison to bool constant Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add comment on exported functions * Fix fixture consistency, fix existing unit tests * Fix boolean comparison in xorm query. * addCollaborator and changeCollaborationAccessMode separate steps More clear to use different if-cases. * Create and commit xorm session * fix * Add information of create repo permission in team sidebar * Add migration step * Clarify that repository creator will be administrator. * Fix some things after merge * Fix language text that use html * migrations file * Create repository permission -> Create repositories * fix merge * fix review comments
This commit is contained in:
		
							parent
							
								
									35c3ea952a
								
							
						
					
					
						commit
						69a255defb
					
				
					 27 changed files with 252 additions and 63 deletions
				
			
		| 
						 | 
				
			
			@ -347,6 +347,8 @@ func TestAPIOrgRepoCreate(t *testing.T) {
 | 
			
		|||
		{ctxUserID: 1, orgName: "user3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
 | 
			
		||||
		{ctxUserID: 2, orgName: "user3", repoName: "repo-own", expectedStatus: http.StatusCreated},
 | 
			
		||||
		{ctxUserID: 2, orgName: "user6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
 | 
			
		||||
		{ctxUserID: 28, orgName: "user3", repoName: "repo-creator", expectedStatus: http.StatusCreated},
 | 
			
		||||
		{ctxUserID: 28, orgName: "user6", repoName: "repo-not-creator", expectedStatus: http.StatusForbidden},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prepareTestEnv(t)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,3 +45,16 @@
 | 
			
		|||
  uid: 24
 | 
			
		||||
  org_id: 25
 | 
			
		||||
  is_public: true
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 9
 | 
			
		||||
  uid: 28
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  is_public: true
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 10
 | 
			
		||||
  uid: 28
 | 
			
		||||
  org_id: 6
 | 
			
		||||
  is_public: true
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,3 +96,23 @@
 | 
			
		|||
  authorize: 1 # read
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 0
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 12
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  lower_name: team12creators
 | 
			
		||||
  name: team12Creators
 | 
			
		||||
  authorize: 3 # admin
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 1
 | 
			
		||||
  can_create_org_repo: true
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 13
 | 
			
		||||
  org_id: 6
 | 
			
		||||
  lower_name: team13notcreators
 | 
			
		||||
  name: team13NotCreators
 | 
			
		||||
  authorize: 3 # admin
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 1
 | 
			
		||||
  can_create_org_repo: false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,3 +69,15 @@
 | 
			
		|||
  org_id: 25
 | 
			
		||||
  team_id: 10
 | 
			
		||||
  uid: 24
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 13
 | 
			
		||||
  org_id: 3
 | 
			
		||||
  team_id: 12
 | 
			
		||||
  uid: 28
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 14
 | 
			
		||||
  org_id: 6
 | 
			
		||||
  team_id: 13
 | 
			
		||||
  uid: 28
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,8 +50,8 @@
 | 
			
		|||
  avatar: avatar3
 | 
			
		||||
  avatar_email: user3@example.com
 | 
			
		||||
  num_repos: 3
 | 
			
		||||
  num_members: 2
 | 
			
		||||
  num_teams: 3
 | 
			
		||||
  num_members: 3
 | 
			
		||||
  num_teams: 4
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 4
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +102,8 @@
 | 
			
		|||
  avatar: avatar6
 | 
			
		||||
  avatar_email: user6@example.com
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_members: 1
 | 
			
		||||
  num_teams: 1
 | 
			
		||||
  num_members: 2
 | 
			
		||||
  num_teams: 2
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 7
 | 
			
		||||
| 
						 | 
				
			
			@ -443,3 +443,23 @@
 | 
			
		|||
  avatar: avatar27
 | 
			
		||||
  avatar_email: user27@example.com
 | 
			
		||||
  num_repos: 2
 | 
			
		||||
 | 
			
		||||
-
 | 
			
		||||
  id: 28
 | 
			
		||||
  lower_name: user28
 | 
			
		||||
  name: user28
 | 
			
		||||
  full_name: "user27"
 | 
			
		||||
  email: user28@example.com
 | 
			
		||||
  keep_email_private: true
 | 
			
		||||
  passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
 | 
			
		||||
  type: 0 # individual
 | 
			
		||||
  salt: ZogKvWdyEx
 | 
			
		||||
  is_admin: false
 | 
			
		||||
  avatar: avatar28
 | 
			
		||||
  avatar_email: user28@example.com
 | 
			
		||||
  num_repos: 0
 | 
			
		||||
  num_stars: 0
 | 
			
		||||
  num_followers: 0
 | 
			
		||||
  num_following: 0
 | 
			
		||||
  is_active: true
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -272,6 +272,8 @@ var migrations = []Migration{
 | 
			
		|||
	NewMigration("Add template options to repository", addTemplateToRepo),
 | 
			
		||||
	// v108 -> v109
 | 
			
		||||
	NewMigration("Add comment_id on table notification", addCommentIDOnNotification),
 | 
			
		||||
	// v109 -> v110
 | 
			
		||||
	NewMigration("add can_create_org_repo to team", addCanCreateOrgRepoColumnForTeam),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Migrate database to current version
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										17
									
								
								models/migrations/v109.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								models/migrations/v109.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
// Copyright 2019 The Gitea 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
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"xorm.io/xorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func addCanCreateOrgRepoColumnForTeam(x *xorm.Engine) error {
 | 
			
		||||
	type Team struct {
 | 
			
		||||
		CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return x.Sync2(new(Team))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +29,11 @@ func (org *User) IsOrgMember(uid int64) (bool, error) {
 | 
			
		|||
	return IsOrganizationMember(org.ID, uid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CanCreateOrgRepo returns true if given user can create repo in organization
 | 
			
		||||
func (org *User) CanCreateOrgRepo(uid int64) (bool, error) {
 | 
			
		||||
	return CanCreateOrgRepo(org.ID, uid)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (org *User) getTeam(e Engine, name string) (*Team, error) {
 | 
			
		||||
	return getTeam(e, org.ID, name)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -158,6 +163,7 @@ func CreateOrganization(org, owner *User) (err error) {
 | 
			
		|||
		Authorize:               AccessModeOwner,
 | 
			
		||||
		NumMembers:              1,
 | 
			
		||||
		IncludesAllRepositories: true,
 | 
			
		||||
		CanCreateOrgRepo:        true,
 | 
			
		||||
	}
 | 
			
		||||
	if _, err = sess.Insert(t); err != nil {
 | 
			
		||||
		return fmt.Errorf("insert owner team: %v", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +345,19 @@ func IsPublicMembership(orgID, uid int64) (bool, error) {
 | 
			
		|||
		Exist()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CanCreateOrgRepo returns true if user can create repo in organization
 | 
			
		||||
func CanCreateOrgRepo(orgID, uid int64) (bool, error) {
 | 
			
		||||
	if owner, err := IsOrganizationOwner(orgID, uid); owner || err != nil {
 | 
			
		||||
		return owner, err
 | 
			
		||||
	}
 | 
			
		||||
	return x.
 | 
			
		||||
		Where(builder.Eq{"team.can_create_org_repo": true}).
 | 
			
		||||
		Join("INNER", "team_user", "team_user.team_id = team.id").
 | 
			
		||||
		And("team_user.uid = ?", uid).
 | 
			
		||||
		And("team_user.org_id = ?", orgID).
 | 
			
		||||
		Exist(new(Team))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getOrgsByUserID(sess *xorm.Session, userID int64, showAll bool) ([]*User, error) {
 | 
			
		||||
	orgs := make([]*User, 0, 10)
 | 
			
		||||
	if !showAll {
 | 
			
		||||
| 
						 | 
				
			
			@ -418,6 +437,19 @@ func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
 | 
			
		|||
	return getOwnedOrgsByUserID(x.Desc(desc), userID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
 | 
			
		||||
// are allowed to create repos.
 | 
			
		||||
func GetOrgsCanCreateRepoByUserID(userID int64) ([]*User, error) {
 | 
			
		||||
	orgs := make([]*User, 0, 10)
 | 
			
		||||
 | 
			
		||||
	return orgs, x.Join("INNER", "`team_user`", "`team_user`.org_id=`user`.id").
 | 
			
		||||
		Join("INNER", "`team`", "`team`.id=`team_user`.team_id").
 | 
			
		||||
		Where("`team_user`.uid=?", userID).
 | 
			
		||||
		And(builder.Eq{"`team`.authorize": AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})).
 | 
			
		||||
		Desc("`user`.updated_unix").
 | 
			
		||||
		Find(&orgs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetOrgUsersByUserID returns all organization-user relations by user ID.
 | 
			
		||||
func GetOrgUsersByUserID(uid int64, all bool) ([]*OrgUser, error) {
 | 
			
		||||
	ous := make([]*OrgUser, 0, 10)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ type Team struct {
 | 
			
		|||
	NumMembers              int
 | 
			
		||||
	Units                   []*TeamUnit `xorm:"-"`
 | 
			
		||||
	IncludesAllRepositories bool        `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
	CanCreateOrgRepo        bool        `xorm:"NOT NULL DEFAULT false"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchTeamOptions holds the search options
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,10 +87,11 @@ func TestUser_GetTeams(t *testing.T) {
 | 
			
		|||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
 | 
			
		||||
	assert.NoError(t, org.GetTeams())
 | 
			
		||||
	if assert.Len(t, org.Teams, 3) {
 | 
			
		||||
	if assert.Len(t, org.Teams, 4) {
 | 
			
		||||
		assert.Equal(t, int64(1), org.Teams[0].ID)
 | 
			
		||||
		assert.Equal(t, int64(2), org.Teams[1].ID)
 | 
			
		||||
		assert.Equal(t, int64(7), org.Teams[2].ID)
 | 
			
		||||
		assert.Equal(t, int64(12), org.Teams[2].ID)
 | 
			
		||||
		assert.Equal(t, int64(7), org.Teams[3].ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,9 +99,10 @@ func TestUser_GetMembers(t *testing.T) {
 | 
			
		|||
	assert.NoError(t, PrepareTestDatabase())
 | 
			
		||||
	org := AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
 | 
			
		||||
	assert.NoError(t, org.GetMembers())
 | 
			
		||||
	if assert.Len(t, org.Members, 2) {
 | 
			
		||||
	if assert.Len(t, org.Members, 3) {
 | 
			
		||||
		assert.Equal(t, int64(2), org.Members[0].ID)
 | 
			
		||||
		assert.Equal(t, int64(4), org.Members[1].ID)
 | 
			
		||||
		assert.Equal(t, int64(28), org.Members[1].ID)
 | 
			
		||||
		assert.Equal(t, int64(4), org.Members[2].ID)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -395,7 +397,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	orgUsers, err := GetOrgUsersByOrgID(3)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	if assert.Len(t, orgUsers, 2) {
 | 
			
		||||
	if assert.Len(t, orgUsers, 3) {
 | 
			
		||||
		assert.Equal(t, OrgUser{
 | 
			
		||||
			ID:       orgUsers[0].ID,
 | 
			
		||||
			OrgID:    3,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1586,6 +1586,18 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
 | 
			
		|||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if isAdmin, err := isUserRepoAdmin(e, repo, doer); err != nil {
 | 
			
		||||
			return fmt.Errorf("isUserRepoAdmin: %v", err)
 | 
			
		||||
		} else if !isAdmin {
 | 
			
		||||
			// Make creator repo admin if it wan't assigned automatically
 | 
			
		||||
			if err = repo.addCollaborator(e, doer); err != nil {
 | 
			
		||||
				return fmt.Errorf("AddCollaborator: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			if err = repo.changeCollaborationAccessMode(e, doer.ID, AccessModeAdmin); err != nil {
 | 
			
		||||
				return fmt.Errorf("ChangeCollaborationAccessMode: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if err = repo.recalculateAccesses(e); err != nil {
 | 
			
		||||
		// Organization automatically called this in addRepository method.
 | 
			
		||||
		return fmt.Errorf("recalculateAccesses: %v", err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,14 +16,13 @@ type Collaboration struct {
 | 
			
		|||
	Mode   AccessMode `xorm:"DEFAULT 2 NOT NULL"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddCollaborator adds new collaboration to a repository with default access mode.
 | 
			
		||||
func (repo *Repository) AddCollaborator(u *User) error {
 | 
			
		||||
func (repo *Repository) addCollaborator(e Engine, u *User) error {
 | 
			
		||||
	collaboration := &Collaboration{
 | 
			
		||||
		RepoID: repo.ID,
 | 
			
		||||
		UserID: u.ID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has, err := x.Get(collaboration)
 | 
			
		||||
	has, err := e.Get(collaboration)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	} else if has {
 | 
			
		||||
| 
						 | 
				
			
			@ -31,20 +30,25 @@ func (repo *Repository) AddCollaborator(u *User) error {
 | 
			
		|||
	}
 | 
			
		||||
	collaboration.Mode = AccessModeWrite
 | 
			
		||||
 | 
			
		||||
	if _, err = e.InsertOne(collaboration); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return repo.recalculateUserAccess(e, u.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddCollaborator adds new collaboration to a repository with default access mode.
 | 
			
		||||
func (repo *Repository) AddCollaborator(u *User) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = sess.InsertOne(collaboration); err != nil {
 | 
			
		||||
	if err := repo.addCollaborator(sess, u); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = repo.recalculateUserAccess(sess, u.ID); err != nil {
 | 
			
		||||
		return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -105,8 +109,7 @@ func (repo *Repository) IsCollaborator(userID int64) (bool, error) {
 | 
			
		|||
	return repo.isCollaborator(x, userID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
 | 
			
		||||
func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error {
 | 
			
		||||
func (repo *Repository) changeCollaborationAccessMode(e Engine, uid int64, mode AccessMode) error {
 | 
			
		||||
	// Discard invalid input
 | 
			
		||||
	if mode <= AccessModeNone || mode > AccessModeOwner {
 | 
			
		||||
		return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +119,7 @@ func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode
 | 
			
		|||
		RepoID: repo.ID,
 | 
			
		||||
		UserID: uid,
 | 
			
		||||
	}
 | 
			
		||||
	has, err := x.Get(collaboration)
 | 
			
		||||
	has, err := e.Get(collaboration)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("get collaboration: %v", err)
 | 
			
		||||
	} else if !has {
 | 
			
		||||
| 
						 | 
				
			
			@ -128,21 +131,30 @@ func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode
 | 
			
		|||
	}
 | 
			
		||||
	collaboration.Mode = mode
 | 
			
		||||
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err = sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = sess.
 | 
			
		||||
	if _, err = e.
 | 
			
		||||
		ID(collaboration.ID).
 | 
			
		||||
		Cols("mode").
 | 
			
		||||
		Update(collaboration); err != nil {
 | 
			
		||||
		return fmt.Errorf("update collaboration: %v", err)
 | 
			
		||||
	} else if _, err = sess.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
 | 
			
		||||
	} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
 | 
			
		||||
		return fmt.Errorf("update access table: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
 | 
			
		||||
func (repo *Repository) ChangeCollaborationAccessMode(uid int64, mode AccessMode) error {
 | 
			
		||||
	sess := x.NewSession()
 | 
			
		||||
	defer sess.Close()
 | 
			
		||||
	if err := sess.Begin(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo.changeCollaborationAccessMode(sess, uid, mode); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sess.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -153,13 +153,13 @@ func TestSearchUsers(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1},
 | 
			
		||||
		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27})
 | 
			
		||||
		[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28})
 | 
			
		||||
 | 
			
		||||
	testUserSuccess(&SearchUserOptions{Page: 1, IsActive: util.OptionalBoolFalse},
 | 
			
		||||
		[]int64{9})
 | 
			
		||||
 | 
			
		||||
	testUserSuccess(&SearchUserOptions{OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
 | 
			
		||||
		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24})
 | 
			
		||||
		[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 28})
 | 
			
		||||
 | 
			
		||||
	testUserSuccess(&SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", Page: 1, IsActive: util.OptionalBoolTrue},
 | 
			
		||||
		[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,8 +17,8 @@ func TestUserListIsPublicMember(t *testing.T) {
 | 
			
		|||
		orgid    int64
 | 
			
		||||
		expected map[int64]bool
 | 
			
		||||
	}{
 | 
			
		||||
		{3, map[int64]bool{2: true, 4: false}},
 | 
			
		||||
		{6, map[int64]bool{5: true}},
 | 
			
		||||
		{3, map[int64]bool{2: true, 4: false, 28: true}},
 | 
			
		||||
		{6, map[int64]bool{5: true, 28: true}},
 | 
			
		||||
		{7, map[int64]bool{5: false}},
 | 
			
		||||
		{25, map[int64]bool{24: true}},
 | 
			
		||||
		{22, map[int64]bool{}},
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +43,8 @@ func TestUserListIsUserOrgOwner(t *testing.T) {
 | 
			
		|||
		orgid    int64
 | 
			
		||||
		expected map[int64]bool
 | 
			
		||||
	}{
 | 
			
		||||
		{3, map[int64]bool{2: true, 4: false}},
 | 
			
		||||
		{6, map[int64]bool{5: true}},
 | 
			
		||||
		{3, map[int64]bool{2: true, 4: false, 28: false}},
 | 
			
		||||
		{6, map[int64]bool{5: true, 28: false}},
 | 
			
		||||
		{7, map[int64]bool{5: true}},
 | 
			
		||||
		{25, map[int64]bool{24: false}}, // ErrTeamNotExist
 | 
			
		||||
		{22, map[int64]bool{}},          // No member
 | 
			
		||||
| 
						 | 
				
			
			@ -69,8 +69,8 @@ func TestUserListIsTwoFaEnrolled(t *testing.T) {
 | 
			
		|||
		orgid    int64
 | 
			
		||||
		expected map[int64]bool
 | 
			
		||||
	}{
 | 
			
		||||
		{3, map[int64]bool{2: false, 4: false}},
 | 
			
		||||
		{6, map[int64]bool{5: false}},
 | 
			
		||||
		{3, map[int64]bool{2: false, 4: false, 28: false}},
 | 
			
		||||
		{6, map[int64]bool{5: false, 28: false}},
 | 
			
		||||
		{7, map[int64]bool{5: false}},
 | 
			
		||||
		{25, map[int64]bool{24: true}},
 | 
			
		||||
		{22, map[int64]bool{}},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,11 +58,12 @@ func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Error
 | 
			
		|||
 | 
			
		||||
// CreateTeamForm form for creating team
 | 
			
		||||
type CreateTeamForm struct {
 | 
			
		||||
	TeamName    string `binding:"Required;AlphaDashDot;MaxSize(30)"`
 | 
			
		||||
	Description string `binding:"MaxSize(255)"`
 | 
			
		||||
	Permission  string
 | 
			
		||||
	Units       []models.UnitType
 | 
			
		||||
	RepoAccess  string
 | 
			
		||||
	TeamName         string `binding:"Required;AlphaDashDot;MaxSize(30)"`
 | 
			
		||||
	Description      string `binding:"MaxSize(255)"`
 | 
			
		||||
	Permission       string
 | 
			
		||||
	Units            []models.UnitType
 | 
			
		||||
	RepoAccess       string
 | 
			
		||||
	CanCreateOrgRepo bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates the fields
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,12 +15,13 @@ import (
 | 
			
		|||
 | 
			
		||||
// Organization contains organization context
 | 
			
		||||
type Organization struct {
 | 
			
		||||
	IsOwner      bool
 | 
			
		||||
	IsMember     bool
 | 
			
		||||
	IsTeamMember bool // Is member of team.
 | 
			
		||||
	IsTeamAdmin  bool // In owner team or team that has admin permission level.
 | 
			
		||||
	Organization *models.User
 | 
			
		||||
	OrgLink      string
 | 
			
		||||
	IsOwner          bool
 | 
			
		||||
	IsMember         bool
 | 
			
		||||
	IsTeamMember     bool // Is member of team.
 | 
			
		||||
	IsTeamAdmin      bool // In owner team or team that has admin permission level.
 | 
			
		||||
	Organization     *models.User
 | 
			
		||||
	OrgLink          string
 | 
			
		||||
	CanCreateOrgRepo bool
 | 
			
		||||
 | 
			
		||||
	Team *models.Team
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +74,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
			
		|||
		ctx.Org.IsMember = true
 | 
			
		||||
		ctx.Org.IsTeamMember = true
 | 
			
		||||
		ctx.Org.IsTeamAdmin = true
 | 
			
		||||
		ctx.Org.CanCreateOrgRepo = true
 | 
			
		||||
	} else if ctx.IsSigned {
 | 
			
		||||
		ctx.Org.IsOwner, err = org.IsOwnedBy(ctx.User.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,12 +86,18 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
			
		|||
			ctx.Org.IsMember = true
 | 
			
		||||
			ctx.Org.IsTeamMember = true
 | 
			
		||||
			ctx.Org.IsTeamAdmin = true
 | 
			
		||||
			ctx.Org.CanCreateOrgRepo = true
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Org.IsMember, err = org.IsOrgMember(ctx.User.ID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("IsOrgMember", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Org.CanCreateOrgRepo, err = org.CanCreateOrgRepo(ctx.User.ID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("CanCreateOrgRepo", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Fake data.
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +110,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
 | 
			
		|||
	}
 | 
			
		||||
	ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
 | 
			
		||||
	ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
 | 
			
		||||
	ctx.Data["CanCreateOrgRepo"] = ctx.Org.CanCreateOrgRepo
 | 
			
		||||
 | 
			
		||||
	ctx.Org.OrgLink = setting.AppSubURL + "/org/" + org.Name
 | 
			
		||||
	ctx.Data["OrgLink"] = ctx.Org.OrgLink
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -249,6 +249,7 @@ func ToTeam(team *models.Team) *api.Team {
 | 
			
		|||
		Name:                    team.Name,
 | 
			
		||||
		Description:             team.Description,
 | 
			
		||||
		IncludesAllRepositories: team.IncludesAllRepositories,
 | 
			
		||||
		CanCreateOrgRepo:        team.CanCreateOrgRepo,
 | 
			
		||||
		Permission:              team.Authorize.String(),
 | 
			
		||||
		Units:                   team.GetUnitNames(),
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,8 @@ type Team struct {
 | 
			
		|||
	// enum: none,read,write,admin,owner
 | 
			
		||||
	Permission string `json:"permission"`
 | 
			
		||||
	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
 | 
			
		||||
	Units []string `json:"units"`
 | 
			
		||||
	Units            []string `json:"units"`
 | 
			
		||||
	CanCreateOrgRepo bool     `json:"can_create_org_repo"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateTeamOption options for creating a team
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +28,8 @@ type CreateTeamOption struct {
 | 
			
		|||
	// enum: read,write,admin
 | 
			
		||||
	Permission string `json:"permission"`
 | 
			
		||||
	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
 | 
			
		||||
	Units []string `json:"units"`
 | 
			
		||||
	Units            []string `json:"units"`
 | 
			
		||||
	CanCreateOrgRepo bool     `json:"can_create_org_repo"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditTeamOption options for editing a team
 | 
			
		||||
| 
						 | 
				
			
			@ -39,5 +41,6 @@ type EditTeamOption struct {
 | 
			
		|||
	// enum: read,write,admin
 | 
			
		||||
	Permission string `json:"permission"`
 | 
			
		||||
	// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
 | 
			
		||||
	Units []string `json:"units"`
 | 
			
		||||
	Units            []string `json:"units"`
 | 
			
		||||
	CanCreateOrgRepo bool     `json:"can_create_org_repo"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1596,6 +1596,8 @@ members.invite_now = Invite Now
 | 
			
		|||
 | 
			
		||||
teams.join = Join
 | 
			
		||||
teams.leave = Leave
 | 
			
		||||
teams.can_create_org_repo = Create repositories
 | 
			
		||||
teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository.
 | 
			
		||||
teams.read_access = Read Access
 | 
			
		||||
teams.read_access_helper = Members can view and clone team repositories.
 | 
			
		||||
teams.write_access = Write Access
 | 
			
		||||
| 
						 | 
				
			
			@ -1615,6 +1617,7 @@ teams.delete_team_success = The team has been deleted.
 | 
			
		|||
teams.read_permission_desc = This team grants <strong>Read</strong> access: members can view and clone team repositories.
 | 
			
		||||
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to team repositories.
 | 
			
		||||
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to and add collaborators to team repositories.
 | 
			
		||||
teams.create_repo_permission_desc = Additionally, this team grants <strong>Create repository</strong> permission: members can create new repositories in organization.
 | 
			
		||||
teams.repositories = Team Repositories
 | 
			
		||||
teams.search_repo_placeholder = Search repository…
 | 
			
		||||
teams.remove_all_repos_title = Remove all team repositories
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,6 +132,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
 | 
			
		|||
		Name:                    form.Name,
 | 
			
		||||
		Description:             form.Description,
 | 
			
		||||
		IncludesAllRepositories: form.IncludesAllRepositories,
 | 
			
		||||
		CanCreateOrgRepo:        form.CanCreateOrgRepo,
 | 
			
		||||
		Authorize:               models.ParseAccessMode(form.Permission),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -185,6 +186,7 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
 | 
			
		|||
	team := ctx.Org.Team
 | 
			
		||||
	team.Description = form.Description
 | 
			
		||||
	unitTypes := models.FindUnitTypes(form.Units...)
 | 
			
		||||
	team.CanCreateOrgRepo = form.CanCreateOrgRepo
 | 
			
		||||
 | 
			
		||||
	isAuthChanged := false
 | 
			
		||||
	isIncludeAllChanged := false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -322,12 +322,12 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if !ctx.User.IsAdmin {
 | 
			
		||||
		isOwner, err := org.IsOwnedBy(ctx.User.ID)
 | 
			
		||||
		canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("IsOwnedBy", err)
 | 
			
		||||
			ctx.ServerError("CanCreateOrgRepo", err)
 | 
			
		||||
			return
 | 
			
		||||
		} else if !isOwner {
 | 
			
		||||
			ctx.Error(403, "", "Given user is not owner of organization.")
 | 
			
		||||
		} else if !canCreate {
 | 
			
		||||
			ctx.Error(403, "", "Given user is not allowed to create repository in organization.")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -201,6 +201,7 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 | 
			
		|||
		Description:             form.Description,
 | 
			
		||||
		Authorize:               models.ParseAccessMode(form.Permission),
 | 
			
		||||
		IncludesAllRepositories: includesAllRepositories,
 | 
			
		||||
		CanCreateOrgRepo:        form.CanCreateOrgRepo,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if t.Authorize < models.AccessModeOwner {
 | 
			
		||||
| 
						 | 
				
			
			@ -316,6 +317,7 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
 | 
			
		|||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	t.CanCreateOrgRepo = form.CanCreateOrgRepo
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
		ctx.HTML(200, tplTeamNew)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,9 +53,9 @@ func MustBeAbleToUpload(ctx *context.Context) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func checkContextUser(ctx *context.Context, uid int64) *models.User {
 | 
			
		||||
	orgs, err := models.GetOwnedOrgsByUserIDDesc(ctx.User.ID, "updated_unix")
 | 
			
		||||
	orgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetOwnedOrgsByUserIDDesc", err)
 | 
			
		||||
		ctx.ServerError("GetOrgsCanCreateRepoByUserID", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Orgs"] = orgs
 | 
			
		||||
| 
						 | 
				
			
			@ -81,11 +81,11 @@ func checkContextUser(ctx *context.Context, uid int64) *models.User {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !ctx.User.IsAdmin {
 | 
			
		||||
		isOwner, err := org.IsOwnedBy(ctx.User.ID)
 | 
			
		||||
		canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.ServerError("IsOwnedBy", err)
 | 
			
		||||
			ctx.ServerError("CanCreateOrgRepo", err)
 | 
			
		||||
			return nil
 | 
			
		||||
		} else if !isOwner {
 | 
			
		||||
		} else if !canCreate {
 | 
			
		||||
			ctx.Error(403)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@
 | 
			
		|||
	<div class="ui container">
 | 
			
		||||
		<div class="ui mobile reversed stackable grid">
 | 
			
		||||
			<div class="ui eleven wide column">
 | 
			
		||||
				{{if .IsOrganizationOwner}}
 | 
			
		||||
				{{if .CanCreateOrgRepo}}
 | 
			
		||||
					<div class="text right">
 | 
			
		||||
						<a class="ui green button" href="{{AppSubUrl}}/repo/create?org={{.Org.ID}}"><i class="octicon octicon-repo-create"></i> {{.i18n.Tr "new_repo"}}</a>
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,14 +31,22 @@
 | 
			
		|||
								<div class="ui radio checkbox">
 | 
			
		||||
									<input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}>
 | 
			
		||||
									<label>{{.i18n.Tr "org.teams.specific_repositories"}}</label>
 | 
			
		||||
									<span class="help">{{.i18n.Tr "org.teams.specific_repositories_helper"}}</span>
 | 
			
		||||
									<span class="help">{{.i18n.Tr "org.teams.specific_repositories_helper" | Str2html}}</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="field">
 | 
			
		||||
								<div class="ui radio checkbox">
 | 
			
		||||
									<input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}>
 | 
			
		||||
									<label>{{.i18n.Tr "org.teams.all_repositories"}}</label>
 | 
			
		||||
									<span class="help">{{.i18n.Tr "org.teams.all_repositories_helper"}}</span>
 | 
			
		||||
									<span class="help">{{.i18n.Tr "org.teams.all_repositories_helper" | Str2html}}</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div class="field">
 | 
			
		||||
								<div class="ui checkbox">
 | 
			
		||||
									<label for="can_create_org_repo">{{.i18n.Tr "org.teams.can_create_org_repo"}}</label>
 | 
			
		||||
									<input id="can_create_org_repo" name="can_create_org_repo" type="checkbox" {{if .Team.CanCreateOrgRepo}}checked{{end}}>
 | 
			
		||||
									<span class="help">{{.i18n.Tr "org.teams.can_create_org_repo_helper"}}</span>
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,9 @@
 | 
			
		|||
					{{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
 | 
			
		||||
				{{end}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
			{{if .Team.CanCreateOrgRepo}}
 | 
			
		||||
				<br><br>{{.i18n.Tr "org.teams.create_repo_permission_desc" | Str2html}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	{{if .IsOrganizationOwner}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8279,6 +8279,10 @@
 | 
			
		|||
        "name"
 | 
			
		||||
      ],
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "can_create_org_repo": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "CanCreateOrgRepo"
 | 
			
		||||
        },
 | 
			
		||||
        "description": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Description"
 | 
			
		||||
| 
						 | 
				
			
			@ -8847,6 +8851,10 @@
 | 
			
		|||
        "name"
 | 
			
		||||
      ],
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "can_create_org_repo": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "CanCreateOrgRepo"
 | 
			
		||||
        },
 | 
			
		||||
        "description": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Description"
 | 
			
		||||
| 
						 | 
				
			
			@ -10506,6 +10514,10 @@
 | 
			
		|||
      "description": "Team represents a team in an organization",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "can_create_org_repo": {
 | 
			
		||||
          "type": "boolean",
 | 
			
		||||
          "x-go-name": "CanCreateOrgRepo"
 | 
			
		||||
        },
 | 
			
		||||
        "description": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Description"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue