Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Team permission to create repository in organization #8312

Merged
merged 28 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8d98450
Add team permission setting to allow creating repo in organization.
davidsvantesson Sep 24, 2019
81a6135
Add test case for creating repo when have team creation access.
davidsvantesson Sep 24, 2019
f20cfd3
build error: should omit comparison to bool constant
davidsvantesson Sep 29, 2019
64a33f6
Add comment on exported functions
davidsvantesson Sep 29, 2019
f59f511
Fix fixture consistency, fix existing unit tests
davidsvantesson Sep 29, 2019
51a8713
Merge branch 'master' into team-permission-create-repo
davidsvantesson Sep 29, 2019
71f5c00
Fix boolean comparison in xorm query.
davidsvantesson Sep 29, 2019
d3eb84f
addCollaborator and changeCollaborationAccessMode separate steps
davidsvantesson Sep 30, 2019
e2080e8
Create and commit xorm session
davidsvantesson Sep 30, 2019
9da17c1
fix
davidsvantesson Sep 30, 2019
af050d9
Add information of create repo permission in team sidebar
davidsvantesson Sep 30, 2019
c2882c0
Add migration step
davidsvantesson Sep 30, 2019
3bf22f8
Clarify that repository creator will be administrator.
davidsvantesson Oct 3, 2019
03376f9
Merge branch 'master' into team-permission-create-repo
davidsvantesson Nov 7, 2019
dbe3935
Fix some things after merge
davidsvantesson Nov 7, 2019
f5e5d1f
Fix language text that use html
davidsvantesson Nov 7, 2019
e0830aa
migrations file
davidsvantesson Nov 7, 2019
0d00ba1
Create repository permission -> Create repositories
davidsvantesson Nov 7, 2019
1cb2e47
Merge branch 'master' into team-permission-create-repo
davidsvantesson Nov 11, 2019
1c58cba
Merge branch 'master' into team-permission-create-repo
davidsvantesson Nov 11, 2019
55f7cb3
fix merge
davidsvantesson Nov 12, 2019
8e14c34
fix review comments
davidsvantesson Nov 12, 2019
7215d73
Merge branch 'master' into team-permission-create-repo
davidsvantesson Nov 12, 2019
6cae327
Merge branch 'master' into team-permission-create-repo
davidsvantesson Nov 13, 2019
00d69d8
Merge branch 'master' into team-permission-create-repo
lunny Nov 15, 2019
36df99d
Merge branch 'master' into team-permission-create-repo
zeripath Nov 15, 2019
0d6b07a
Merge branch 'master' into team-permission-create-repo
lunny Nov 17, 2019
c5ead25
Merge branch 'master' into team-permission-create-repo
lafriks Nov 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions integrations/api_repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions models/fixtures/org_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

20 changes: 20 additions & 0 deletions models/fixtures/team.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions models/fixtures/team_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 24 additions & 4 deletions models/fixtures/user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
avatar: avatar3
avatar_email: [email protected]
num_repos: 3
num_members: 2
num_teams: 3
num_members: 3
num_teams: 4

-
id: 4
Expand Down Expand Up @@ -102,8 +102,8 @@
avatar: avatar6
avatar_email: [email protected]
num_repos: 0
num_members: 1
num_teams: 1
num_members: 2
num_teams: 2

-
id: 7
Expand Down Expand Up @@ -443,3 +443,23 @@
avatar: avatar27
avatar_email: [email protected]
num_repos: 2

-
id: 28
lower_name: user28
name: user28
full_name: "user27"
email: [email protected]
keep_email_private: true
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
type: 0 # individual
salt: ZogKvWdyEx
is_admin: false
avatar: avatar28
avatar_email: [email protected]
num_repos: 0
num_stars: 0
num_followers: 0
num_following: 0
is_active: true

2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions models/migrations/v109.go
Original file line number Diff line number Diff line change
@@ -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))
}
32 changes: 32 additions & 0 deletions models/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions models/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,22 @@ 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)
}
}

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)
}
}

Expand Down Expand Up @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,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)
Expand Down
52 changes: 32 additions & 20 deletions models/repo_collaboration.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,37 @@ 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 {
return nil
}
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()
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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()
}

Expand Down
4 changes: 2 additions & 2 deletions models/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
Loading