mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	Add teams to repo on collaboration page. (#8045)
* Add teams to repo on collaboration page. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add option for repository admins to change teams access to repo. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add comment for functions Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make RepoAdminChangeTeamAccess default false in xorm and make it default checked in template instead. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Make proper language strings and fix error redirection. * Add unit tests for adding and deleting team from repository. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Add database migration Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix redirect Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Fix locale string mismatch. Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Move team access mode text logic to template. * Move collaborator access mode text logic to template.
This commit is contained in:
		
				
					committed by
					
						
						Lauris BH
					
				
			
			
				
	
			
			
			
						parent
						
							63ff61615e
						
					
				
				
					commit
					a0e88dfc2e
				
			@@ -206,14 +206,15 @@ func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
 | 
			
		||||
// ToOrganization convert models.User to api.Organization
 | 
			
		||||
func ToOrganization(org *models.User) *api.Organization {
 | 
			
		||||
	return &api.Organization{
 | 
			
		||||
		ID:          org.ID,
 | 
			
		||||
		AvatarURL:   org.AvatarLink(),
 | 
			
		||||
		UserName:    org.Name,
 | 
			
		||||
		FullName:    org.FullName,
 | 
			
		||||
		Description: org.Description,
 | 
			
		||||
		Website:     org.Website,
 | 
			
		||||
		Location:    org.Location,
 | 
			
		||||
		Visibility:  org.Visibility.String(),
 | 
			
		||||
		ID:                        org.ID,
 | 
			
		||||
		AvatarURL:                 org.AvatarLink(),
 | 
			
		||||
		UserName:                  org.Name,
 | 
			
		||||
		FullName:                  org.FullName,
 | 
			
		||||
		Description:               org.Description,
 | 
			
		||||
		Website:                   org.Website,
 | 
			
		||||
		Location:                  org.Location,
 | 
			
		||||
		Visibility:                org.Visibility.String(),
 | 
			
		||||
		RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -95,14 +95,15 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		Name:        form.UserName,
 | 
			
		||||
		FullName:    form.FullName,
 | 
			
		||||
		Description: form.Description,
 | 
			
		||||
		Website:     form.Website,
 | 
			
		||||
		Location:    form.Location,
 | 
			
		||||
		IsActive:    true,
 | 
			
		||||
		Type:        models.UserTypeOrganization,
 | 
			
		||||
		Visibility:  visibility,
 | 
			
		||||
		Name:                      form.UserName,
 | 
			
		||||
		FullName:                  form.FullName,
 | 
			
		||||
		Description:               form.Description,
 | 
			
		||||
		Website:                   form.Website,
 | 
			
		||||
		Location:                  form.Location,
 | 
			
		||||
		IsActive:                  true,
 | 
			
		||||
		Type:                      models.UserTypeOrganization,
 | 
			
		||||
		Visibility:                visibility,
 | 
			
		||||
		RepoAdminChangeTeamAccess: form.RepoAdminChangeTeamAccess,
 | 
			
		||||
	}
 | 
			
		||||
	if err := models.CreateOrganization(org, ctx.User); err != nil {
 | 
			
		||||
		if models.IsErrUserAlreadyExist(err) ||
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,7 @@ func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
 | 
			
		||||
	org.Website = form.Website
 | 
			
		||||
	org.Location = form.Location
 | 
			
		||||
	org.Visibility = form.Visibility
 | 
			
		||||
	org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess
 | 
			
		||||
	if err := models.UpdateUser(org); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateUser", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -490,6 +490,18 @@ func Collaboration(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Collaborators"] = users
 | 
			
		||||
 | 
			
		||||
	teams, err := ctx.Repo.Repository.GetRepoTeams()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetRepoTeams", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Teams"] = teams
 | 
			
		||||
	ctx.Data["Repo"] = ctx.Repo.Repository
 | 
			
		||||
	ctx.Data["OrgID"] = ctx.Repo.Repository.OwnerID
 | 
			
		||||
	ctx.Data["OrgName"] = ctx.Repo.Repository.OwnerName
 | 
			
		||||
	ctx.Data["Org"] = ctx.Repo.Repository.Owner
 | 
			
		||||
	ctx.Data["Units"] = models.Units
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(200, tplCollaboration)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -566,6 +578,77 @@ func DeleteCollaboration(ctx *context.Context) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddTeamPost response for adding a team to a repository
 | 
			
		||||
func AddTeamPost(ctx *context.Context) {
 | 
			
		||||
	if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.Query("team")))
 | 
			
		||||
	if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team, err := ctx.Repo.Owner.GetTeam(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrTeamNotExist(err) {
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("form.team_not_exist"))
 | 
			
		||||
			ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("GetTeam", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if team.OrgID != ctx.Repo.Repository.OwnerID {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.team_not_in_organization"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if models.HasTeamRepo(ctx.Repo.Repository.OwnerID, team.ID, ctx.Repo.Repository.ID) {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.add_team_duplicate"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = team.AddRepository(ctx.Repo.Repository); err != nil {
 | 
			
		||||
		ctx.ServerError("team.AddRepository", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success"))
 | 
			
		||||
	ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteTeam response for deleting a team from a repository
 | 
			
		||||
func DeleteTeam(ctx *context.Context) {
 | 
			
		||||
	if !ctx.Repo.Owner.RepoAdminChangeTeamAccess && !ctx.Repo.IsOwner() {
 | 
			
		||||
		ctx.Flash.Error(ctx.Tr("repo.settings.change_team_access_not_allowed"))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team, err := models.GetTeamByID(ctx.QueryInt64("id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetTeamByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = team.RemoveRepository(ctx.Repo.Repository.ID); err != nil {
 | 
			
		||||
		ctx.ServerError("team.RemoveRepositorys", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
 | 
			
		||||
	ctx.JSON(200, map[string]interface{}{
 | 
			
		||||
		"redirect": ctx.Repo.RepoLink + "/settings/collaboration",
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseOwnerAndRepo get repos by owner
 | 
			
		||||
func parseOwnerAndRepo(ctx *context.Context) (*models.User, *models.Repository) {
 | 
			
		||||
	owner, err := models.GetUserByName(ctx.Params(":username"))
 | 
			
		||||
 
 | 
			
		||||
@@ -185,3 +185,196 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) {
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.True(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.Empty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_NotAllowed(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: false,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.False(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_AddTeamTwice(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team11")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    11,
 | 
			
		||||
		OrgID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
	assert.True(t, team.HasRepository(re.ID))
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddTeamPost_NonExistentTeam(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org26/repo43")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("team", "team-non-existent")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org26",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      43,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 26,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        26,
 | 
			
		||||
			LowerName:                 "org26",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	AddTeamPost(ctx)
 | 
			
		||||
	assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
 | 
			
		||||
	assert.NotEmpty(t, ctx.Flash.ErrorMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteTeam(t *testing.T) {
 | 
			
		||||
	models.PrepareTestEnv(t)
 | 
			
		||||
	ctx := test.MockContext(t, "org3/team1/repo3")
 | 
			
		||||
 | 
			
		||||
	ctx.Req.Form.Set("id", "2")
 | 
			
		||||
 | 
			
		||||
	org := &models.User{
 | 
			
		||||
		LowerName: "org3",
 | 
			
		||||
		Type:      models.UserTypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	team := &models.Team{
 | 
			
		||||
		ID:    2,
 | 
			
		||||
		OrgID: 3,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	re := &models.Repository{
 | 
			
		||||
		ID:      3,
 | 
			
		||||
		Owner:   org,
 | 
			
		||||
		OwnerID: 3,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repo := &context.Repository{
 | 
			
		||||
		Owner: &models.User{
 | 
			
		||||
			ID:                        3,
 | 
			
		||||
			LowerName:                 "org3",
 | 
			
		||||
			RepoAdminChangeTeamAccess: true,
 | 
			
		||||
		},
 | 
			
		||||
		Repository: re,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Repo = repo
 | 
			
		||||
 | 
			
		||||
	DeleteTeam(ctx)
 | 
			
		||||
 | 
			
		||||
	assert.False(t, team.HasRepository(re.ID))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -629,6 +629,10 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
			
		||||
				m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
 | 
			
		||||
				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
 | 
			
		||||
				m.Post("/delete", repo.DeleteCollaboration)
 | 
			
		||||
				m.Group("/team", func() {
 | 
			
		||||
					m.Post("", repo.AddTeamPost)
 | 
			
		||||
					m.Post("/delete", repo.DeleteTeam)
 | 
			
		||||
				})
 | 
			
		||||
			})
 | 
			
		||||
			m.Group("/branches", func() {
 | 
			
		||||
				m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user