diff options
author | David Svantesson <davidsvantesson@gmail.com> | 2019-11-20 12:27:49 +0100 |
---|---|---|
committer | Lunny Xiao <xiaolunwen@gmail.com> | 2019-11-20 19:27:49 +0800 |
commit | 69a255defbf2747b066b2aeee66ba76cdd37104d (patch) | |
tree | 8cc6f166c06ff9ac334b3f5f69476fa4394ae6a2 /models | |
parent | 35c3ea952a6eb558e69de55fc94d301676feb935 (diff) | |
download | gitea-69a255defbf2747b066b2aeee66ba76cdd37104d.tar.gz gitea-69a255defbf2747b066b2aeee66ba76cdd37104d.zip |
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
Diffstat (limited to 'models')
-rw-r--r-- | models/fixtures/org_user.yml | 13 | ||||
-rw-r--r-- | models/fixtures/team.yml | 20 | ||||
-rw-r--r-- | models/fixtures/team_user.yml | 12 | ||||
-rw-r--r-- | models/fixtures/user.yml | 28 | ||||
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/migrations/v109.go | 17 | ||||
-rw-r--r-- | models/org.go | 32 | ||||
-rw-r--r-- | models/org_team.go | 1 | ||||
-rw-r--r-- | models/org_test.go | 12 | ||||
-rw-r--r-- | models/repo.go | 12 | ||||
-rw-r--r-- | models/repo_collaboration.go | 52 | ||||
-rw-r--r-- | models/user_test.go | 4 | ||||
-rw-r--r-- | models/userlist_test.go | 12 |
13 files changed, 180 insertions, 37 deletions
diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index 385492dd68..0b6a5e60a7 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -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 + diff --git a/models/fixtures/team.yml b/models/fixtures/team.yml index 4da87b731f..b7e3856172 100644 --- a/models/fixtures/team.yml +++ b/models/fixtures/team.yml @@ -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 diff --git a/models/fixtures/team_user.yml b/models/fixtures/team_user.yml index 4fc609791d..d541156fe8 100644 --- a/models/fixtures/team_user.yml +++ b/models/fixtures/team_user.yml @@ -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 diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 5a3b04994c..17294b881f 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -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 + diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index e5bfc2b881..df82fe9b8b 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -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 diff --git a/models/migrations/v109.go b/models/migrations/v109.go new file mode 100644 index 0000000000..abe7317681 --- /dev/null +++ b/models/migrations/v109.go @@ -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)) +} diff --git a/models/org.go b/models/org.go index 78b035b101..f14dad1dbb 100644 --- a/models/org.go +++ b/models/org.go @@ -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) diff --git a/models/org_team.go b/models/org_team.go index 126a8c896a..2dadf3820c 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -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 diff --git a/models/org_test.go b/models/org_test.go index 2f2c5a2d5e..1a6b288dc7 100644 --- a/models/org_test.go +++ b/models/org_test.go @@ -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, diff --git a/models/repo.go b/models/repo.go index 851add409f..eecc36377b 100644 --- a/models/repo.go +++ b/models/repo.go @@ -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) diff --git a/models/repo_collaboration.go b/models/repo_collaboration.go index 3d6447c196..f04507f3e8 100644 --- a/models/repo_collaboration.go +++ b/models/repo_collaboration.go @@ -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,18 +30,23 @@ func (repo *Repository) AddCollaborator(u *User) error { } collaboration.Mode = AccessModeWrite - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + if _, err = e.InsertOne(collaboration); err != nil { return err } - if _, err = sess.InsertOne(collaboration); err != nil { + 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 { return err } - if err = repo.recalculateUserAccess(sess, u.ID); err != nil { - return fmt.Errorf("recalculateAccesses 'team=%v': %v", repo.Owner.IsOrganization(), err) + if err := repo.addCollaborator(sess, u); err != nil { + return 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() } diff --git a/models/user_test.go b/models/user_test.go index 2969e34a76..95f4d5d363 100644 --- a/models/user_test.go +++ b/models/user_test.go @@ -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}) diff --git a/models/userlist_test.go b/models/userlist_test.go index ca08cc90ce..c48cfb61c1 100644 --- a/models/userlist_test.go +++ b/models/userlist_test.go @@ -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{}}, |