KN4CK3R 2022-10-19 14:40:28 +02:00 committed by GitHub
parent 7d1aed83f4
commit c3b2e44392
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 615 additions and 43 deletions

View file

@ -417,6 +417,8 @@ var migrations = []Migration{
NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField),
// v227 -> v228
NewMigration("Create key/value table for system settings", createSystemSettingsTable),
// v228 -> v229
NewMigration("Add TeamInvite table", addTeamInviteTable),
}
// GetCurrentDBVersion returns the current db version

26
models/migrations/v228.go Normal file
View file

@ -0,0 +1,26 @@
// Copyright 2022 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 (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func addTeamInviteTable(x *xorm.Engine) error {
type TeamInvite struct {
ID int64 `xorm:"pk autoincr"`
Token string `xorm:"UNIQUE(token) INDEX NOT NULL DEFAULT ''"`
InviterID int64 `xorm:"NOT NULL DEFAULT 0"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
TeamID int64 `xorm:"UNIQUE(team_mail) INDEX NOT NULL DEFAULT 0"`
Email string `xorm:"UNIQUE(team_mail) NOT NULL DEFAULT ''"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
return x.Sync2(new(TeamInvite))
}

View file

@ -431,25 +431,15 @@ func DeleteTeam(t *organization.Team) error {
}
}
// Delete team-user.
if _, err := sess.
Where("org_id=?", t.OrgID).
Where("team_id=?", t.ID).
Delete(new(organization.TeamUser)); err != nil {
if err := db.DeleteBeans(ctx,
&organization.Team{ID: t.ID},
&organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
&organization.TeamUnit{TeamID: t.ID},
&organization.TeamInvite{TeamID: t.ID},
); err != nil {
return err
}
// Delete team-unit.
if _, err := sess.
Where("team_id=?", t.ID).
Delete(new(organization.TeamUnit)); err != nil {
return err
}
// Delete team.
if _, err := sess.ID(t.ID).Delete(new(organization.Team)); err != nil {
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 {
return err

View file

@ -370,8 +370,9 @@ func DeleteOrganization(ctx context.Context, org *Organization) error {
&OrgUser{OrgID: org.ID},
&TeamUser{OrgID: org.ID},
&TeamUnit{OrgID: org.ID},
&TeamInvite{OrgID: org.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
return fmt.Errorf("DeleteBeans: %v", err)
}
if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil {

View file

@ -94,6 +94,7 @@ func init() {
db.RegisterModel(new(TeamUser))
db.RegisterModel(new(TeamRepo))
db.RegisterModel(new(TeamUnit))
db.RegisterModel(new(TeamInvite))
}
// SearchTeamOptions holds the search options

View file

@ -0,0 +1,162 @@
// Copyright 2022 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 organization
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder"
)
type ErrTeamInviteAlreadyExist struct {
TeamID int64
Email string
}
func IsErrTeamInviteAlreadyExist(err error) bool {
_, ok := err.(ErrTeamInviteAlreadyExist)
return ok
}
func (err ErrTeamInviteAlreadyExist) Error() string {
return fmt.Sprintf("team invite already exists [team_id: %d, email: %s]", err.TeamID, err.Email)
}
func (err ErrTeamInviteAlreadyExist) Unwrap() error {
return util.ErrAlreadyExist
}
type ErrTeamInviteNotFound struct {
Token string
}
func IsErrTeamInviteNotFound(err error) bool {
_, ok := err.(ErrTeamInviteNotFound)
return ok
}
func (err ErrTeamInviteNotFound) Error() string {
return fmt.Sprintf("team invite was not found [token: %s]", err.Token)
}
func (err ErrTeamInviteNotFound) Unwrap() error {
return util.ErrNotExist
}
// ErrUserEmailAlreadyAdded represents a "user by email already added to team" error.
type ErrUserEmailAlreadyAdded struct {
Email string
}
// IsErrUserEmailAlreadyAdded checks if an error is a ErrUserEmailAlreadyAdded.
func IsErrUserEmailAlreadyAdded(err error) bool {
_, ok := err.(ErrUserEmailAlreadyAdded)
return ok
}
func (err ErrUserEmailAlreadyAdded) Error() string {
return fmt.Sprintf("user with email already added [email: %s]", err.Email)
}
func (err ErrUserEmailAlreadyAdded) Unwrap() error {
return util.ErrAlreadyExist
}
// TeamInvite represents an invite to a team
type TeamInvite struct {
ID int64 `xorm:"pk autoincr"`
Token string `xorm:"UNIQUE(token) INDEX NOT NULL DEFAULT ''"`
InviterID int64 `xorm:"NOT NULL DEFAULT 0"`
OrgID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
TeamID int64 `xorm:"UNIQUE(team_mail) INDEX NOT NULL DEFAULT 0"`
Email string `xorm:"UNIQUE(team_mail) NOT NULL DEFAULT ''"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
func CreateTeamInvite(ctx context.Context, doer *user_model.User, team *Team, email string) (*TeamInvite, error) {
has, err := db.GetEngine(ctx).Exist(&TeamInvite{
TeamID: team.ID,
Email: email,
})
if err != nil {
return nil, err
}
if has {
return nil, ErrTeamInviteAlreadyExist{
TeamID: team.ID,
Email: email,
}
}
// check if the user is already a team member by email
exist, err := db.GetEngine(ctx).
Where(builder.Eq{
"team_user.org_id": team.OrgID,
"team_user.team_id": team.ID,
"`user`.email": email,
}).
Join("INNER", "`user`", "`user`.id = team_user.uid").
Table("team_user").
Exist()
if err != nil {
return nil, err
}
if exist {
return nil, ErrUserEmailAlreadyAdded{
Email: email,
}
}
token, err := util.CryptoRandomString(25)
if err != nil {
return nil, err
}
invite := &TeamInvite{
Token: token,
InviterID: doer.ID,
OrgID: team.OrgID,
TeamID: team.ID,
Email: email,
}
return invite, db.Insert(ctx, invite)
}
func RemoveInviteByID(ctx context.Context, inviteID, teamID int64) error {
_, err := db.DeleteByBean(ctx, &TeamInvite{
ID: inviteID,
TeamID: teamID,
})
return err
}
func GetInvitesByTeamID(ctx context.Context, teamID int64) ([]*TeamInvite, error) {
invites := make([]*TeamInvite, 0, 10)
return invites, db.GetEngine(ctx).
Where("team_id=?", teamID).
Find(&invites)
}
func GetInviteByToken(ctx context.Context, token string) (*TeamInvite, error) {
invite := &TeamInvite{}
has, err := db.GetEngine(ctx).Where("token=?", token).Get(invite)
if err != nil {
return nil, err
}
if !has {
return nil, ErrTeamInviteNotFound{Token: token}
}
return invite, nil
}

View file

@ -0,0 +1,49 @@
// Copyright 2022 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 organization_test
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
func TestTeamInvite(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
t.Run("MailExistsInTeam", func(t *testing.T) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// user 2 already added to team 2, should result in error
_, err := organization.CreateTeamInvite(db.DefaultContext, user2, team, user2.Email)
assert.Error(t, err)
})
t.Run("CreateAndRemove", func(t *testing.T) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
invite, err := organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
assert.NotNil(t, invite)
assert.NoError(t, err)
// Shouldn't allow duplicate invite
_, err = organization.CreateTeamInvite(db.DefaultContext, user1, team, "user3@example.com")
assert.Error(t, err)
// should remove invite
assert.NoError(t, organization.RemoveInviteByID(db.DefaultContext, invite.ID, invite.TeamID))
// invite should not exist
_, err = organization.GetInviteByToken(db.DefaultContext, invite.Token)
assert.Error(t, err)
})
}