code.gitea.io/gitea@v1.21.7/models/organization/org.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package organization
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	"code.gitea.io/gitea/models/perm"
    14  	repo_model "code.gitea.io/gitea/models/repo"
    15  	secret_model "code.gitea.io/gitea/models/secret"
    16  	"code.gitea.io/gitea/models/unit"
    17  	user_model "code.gitea.io/gitea/models/user"
    18  	"code.gitea.io/gitea/modules/log"
    19  	"code.gitea.io/gitea/modules/setting"
    20  	"code.gitea.io/gitea/modules/structs"
    21  	"code.gitea.io/gitea/modules/util"
    22  
    23  	"xorm.io/builder"
    24  )
    25  
    26  // ________                            .__                __  .__
    27  // \_____  \_______  _________    ____ |__|____________ _/  |_|__| ____   ____
    28  //  /   |   \_  __ \/ ___\__  \  /    \|  \___   /\__  \\   __\  |/  _ \ /    \
    29  // /    |    \  | \/ /_/  > __ \|   |  \  |/    /  / __ \|  | |  (  <_> )   |  \
    30  // \_______  /__|  \___  (____  /___|  /__/_____ \(____  /__| |__|\____/|___|  /
    31  //         \/     /_____/     \/     \/         \/     \/                    \/
    32  
    33  // ErrOrgNotExist represents a "OrgNotExist" kind of error.
    34  type ErrOrgNotExist struct {
    35  	ID   int64
    36  	Name string
    37  }
    38  
    39  // IsErrOrgNotExist checks if an error is a ErrOrgNotExist.
    40  func IsErrOrgNotExist(err error) bool {
    41  	_, ok := err.(ErrOrgNotExist)
    42  	return ok
    43  }
    44  
    45  func (err ErrOrgNotExist) Error() string {
    46  	return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name)
    47  }
    48  
    49  func (err ErrOrgNotExist) Unwrap() error {
    50  	return util.ErrNotExist
    51  }
    52  
    53  // ErrLastOrgOwner represents a "LastOrgOwner" kind of error.
    54  type ErrLastOrgOwner struct {
    55  	UID int64
    56  }
    57  
    58  // IsErrLastOrgOwner checks if an error is a ErrLastOrgOwner.
    59  func IsErrLastOrgOwner(err error) bool {
    60  	_, ok := err.(ErrLastOrgOwner)
    61  	return ok
    62  }
    63  
    64  func (err ErrLastOrgOwner) Error() string {
    65  	return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
    66  }
    67  
    68  // ErrUserNotAllowedCreateOrg represents a "UserNotAllowedCreateOrg" kind of error.
    69  type ErrUserNotAllowedCreateOrg struct{}
    70  
    71  // IsErrUserNotAllowedCreateOrg checks if an error is an ErrUserNotAllowedCreateOrg.
    72  func IsErrUserNotAllowedCreateOrg(err error) bool {
    73  	_, ok := err.(ErrUserNotAllowedCreateOrg)
    74  	return ok
    75  }
    76  
    77  func (err ErrUserNotAllowedCreateOrg) Error() string {
    78  	return "user is not allowed to create organizations"
    79  }
    80  
    81  func (err ErrUserNotAllowedCreateOrg) Unwrap() error {
    82  	return util.ErrPermissionDenied
    83  }
    84  
    85  // Organization represents an organization
    86  type Organization user_model.User
    87  
    88  // OrgFromUser converts user to organization
    89  func OrgFromUser(user *user_model.User) *Organization {
    90  	return (*Organization)(user)
    91  }
    92  
    93  // TableName represents the real table name of Organization
    94  func (Organization) TableName() string {
    95  	return "user"
    96  }
    97  
    98  // IsOwnedBy returns true if given user is in the owner team.
    99  func (org *Organization) IsOwnedBy(uid int64) (bool, error) {
   100  	return IsOrganizationOwner(db.DefaultContext, org.ID, uid)
   101  }
   102  
   103  // IsOrgAdmin returns true if given user is in the owner team or an admin team.
   104  func (org *Organization) IsOrgAdmin(uid int64) (bool, error) {
   105  	return IsOrganizationAdmin(db.DefaultContext, org.ID, uid)
   106  }
   107  
   108  // IsOrgMember returns true if given user is member of organization.
   109  func (org *Organization) IsOrgMember(uid int64) (bool, error) {
   110  	return IsOrganizationMember(db.DefaultContext, org.ID, uid)
   111  }
   112  
   113  // CanCreateOrgRepo returns true if given user can create repo in organization
   114  func (org *Organization) CanCreateOrgRepo(uid int64) (bool, error) {
   115  	return CanCreateOrgRepo(db.DefaultContext, org.ID, uid)
   116  }
   117  
   118  // GetTeam returns named team of organization.
   119  func (org *Organization) GetTeam(ctx context.Context, name string) (*Team, error) {
   120  	return GetTeam(ctx, org.ID, name)
   121  }
   122  
   123  // GetOwnerTeam returns owner team of organization.
   124  func (org *Organization) GetOwnerTeam(ctx context.Context) (*Team, error) {
   125  	return org.GetTeam(ctx, OwnerTeamName)
   126  }
   127  
   128  // FindOrgTeams returns all teams of a given organization
   129  func FindOrgTeams(ctx context.Context, orgID int64) ([]*Team, error) {
   130  	var teams []*Team
   131  	return teams, db.GetEngine(ctx).
   132  		Where("org_id=?", orgID).
   133  		OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END").
   134  		Find(&teams)
   135  }
   136  
   137  // LoadTeams load teams if not loaded.
   138  func (org *Organization) LoadTeams() ([]*Team, error) {
   139  	return FindOrgTeams(db.DefaultContext, org.ID)
   140  }
   141  
   142  // GetMembers returns all members of organization.
   143  func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) {
   144  	return FindOrgMembers(ctx, &FindOrgMembersOpts{
   145  		OrgID: org.ID,
   146  	})
   147  }
   148  
   149  // HasMemberWithUserID returns true if user with userID is part of the u organisation.
   150  func (org *Organization) HasMemberWithUserID(userID int64) bool {
   151  	return org.hasMemberWithUserID(db.DefaultContext, userID)
   152  }
   153  
   154  func (org *Organization) hasMemberWithUserID(ctx context.Context, userID int64) bool {
   155  	isMember, err := IsOrganizationMember(ctx, org.ID, userID)
   156  	if err != nil {
   157  		log.Error("IsOrganizationMember: %v", err)
   158  		return false
   159  	}
   160  	return isMember
   161  }
   162  
   163  // AvatarLink returns the full avatar link with http host
   164  func (org *Organization) AvatarLink(ctx context.Context) string {
   165  	return org.AsUser().AvatarLink(ctx)
   166  }
   167  
   168  // HTMLURL returns the organization's full link.
   169  func (org *Organization) HTMLURL() string {
   170  	return org.AsUser().HTMLURL()
   171  }
   172  
   173  // OrganisationLink returns the organization sub page link.
   174  func (org *Organization) OrganisationLink() string {
   175  	return org.AsUser().OrganisationLink()
   176  }
   177  
   178  // ShortName ellipses username to length
   179  func (org *Organization) ShortName(length int) string {
   180  	return org.AsUser().ShortName(length)
   181  }
   182  
   183  // HomeLink returns the user or organization home page link.
   184  func (org *Organization) HomeLink() string {
   185  	return org.AsUser().HomeLink()
   186  }
   187  
   188  // CanCreateRepo returns if user login can create a repository
   189  // NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised
   190  func (org *Organization) CanCreateRepo() bool {
   191  	return org.AsUser().CanCreateRepo()
   192  }
   193  
   194  // FindOrgMembersOpts represensts find org members conditions
   195  type FindOrgMembersOpts struct {
   196  	db.ListOptions
   197  	OrgID      int64
   198  	PublicOnly bool
   199  }
   200  
   201  // CountOrgMembers counts the organization's members
   202  func CountOrgMembers(opts *FindOrgMembersOpts) (int64, error) {
   203  	sess := db.GetEngine(db.DefaultContext).Where("org_id=?", opts.OrgID)
   204  	if opts.PublicOnly {
   205  		sess.And("is_public = ?", true)
   206  	}
   207  	return sess.Count(new(OrgUser))
   208  }
   209  
   210  // FindOrgMembers loads organization members according conditions
   211  func FindOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) {
   212  	ous, err := GetOrgUsersByOrgID(ctx, opts)
   213  	if err != nil {
   214  		return nil, nil, err
   215  	}
   216  
   217  	ids := make([]int64, len(ous))
   218  	idsIsPublic := make(map[int64]bool, len(ous))
   219  	for i, ou := range ous {
   220  		ids[i] = ou.UID
   221  		idsIsPublic[ou.UID] = ou.IsPublic
   222  	}
   223  
   224  	users, err := user_model.GetUsersByIDs(ctx, ids)
   225  	if err != nil {
   226  		return nil, nil, err
   227  	}
   228  	return users, idsIsPublic, nil
   229  }
   230  
   231  // AsUser returns the org as user object
   232  func (org *Organization) AsUser() *user_model.User {
   233  	return (*user_model.User)(org)
   234  }
   235  
   236  // DisplayName returns full name if it's not empty,
   237  // returns username otherwise.
   238  func (org *Organization) DisplayName() string {
   239  	return org.AsUser().DisplayName()
   240  }
   241  
   242  // CustomAvatarRelativePath returns user custom avatar relative path.
   243  func (org *Organization) CustomAvatarRelativePath() string {
   244  	return org.Avatar
   245  }
   246  
   247  // UnitPermission returns unit permission
   248  func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.User, unitType unit.Type) perm.AccessMode {
   249  	if doer != nil {
   250  		teams, err := GetUserOrgTeams(ctx, org.ID, doer.ID)
   251  		if err != nil {
   252  			log.Error("GetUserOrgTeams: %v", err)
   253  			return perm.AccessModeNone
   254  		}
   255  
   256  		if err := teams.LoadUnits(ctx); err != nil {
   257  			log.Error("LoadUnits: %v", err)
   258  			return perm.AccessModeNone
   259  		}
   260  
   261  		if len(teams) > 0 {
   262  			return teams.UnitMaxAccess(unitType)
   263  		}
   264  	}
   265  
   266  	if org.Visibility.IsPublic() {
   267  		return perm.AccessModeRead
   268  	}
   269  
   270  	return perm.AccessModeNone
   271  }
   272  
   273  // CreateOrganization creates record of a new organization.
   274  func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
   275  	if !owner.CanCreateOrganization() {
   276  		return ErrUserNotAllowedCreateOrg{}
   277  	}
   278  
   279  	if err = user_model.IsUsableUsername(org.Name); err != nil {
   280  		return err
   281  	}
   282  
   283  	isExist, err := user_model.IsUserExist(db.DefaultContext, 0, org.Name)
   284  	if err != nil {
   285  		return err
   286  	} else if isExist {
   287  		return user_model.ErrUserAlreadyExist{Name: org.Name}
   288  	}
   289  
   290  	org.LowerName = strings.ToLower(org.Name)
   291  	if org.Rands, err = user_model.GetUserSalt(); err != nil {
   292  		return err
   293  	}
   294  	if org.Salt, err = user_model.GetUserSalt(); err != nil {
   295  		return err
   296  	}
   297  	org.UseCustomAvatar = true
   298  	org.MaxRepoCreation = -1
   299  	org.NumTeams = 1
   300  	org.NumMembers = 1
   301  	org.Type = user_model.UserTypeOrganization
   302  
   303  	ctx, committer, err := db.TxContext(db.DefaultContext)
   304  	if err != nil {
   305  		return err
   306  	}
   307  	defer committer.Close()
   308  
   309  	if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil {
   310  		return err
   311  	}
   312  
   313  	if err = db.Insert(ctx, org); err != nil {
   314  		return fmt.Errorf("insert organization: %w", err)
   315  	}
   316  	if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil {
   317  		return fmt.Errorf("generate random avatar: %w", err)
   318  	}
   319  
   320  	// Add initial creator to organization and owner team.
   321  	if err = db.Insert(ctx, &OrgUser{
   322  		UID:   owner.ID,
   323  		OrgID: org.ID,
   324  	}); err != nil {
   325  		return fmt.Errorf("insert org-user relation: %w", err)
   326  	}
   327  
   328  	// Create default owner team.
   329  	t := &Team{
   330  		OrgID:                   org.ID,
   331  		LowerName:               strings.ToLower(OwnerTeamName),
   332  		Name:                    OwnerTeamName,
   333  		AccessMode:              perm.AccessModeOwner,
   334  		NumMembers:              1,
   335  		IncludesAllRepositories: true,
   336  		CanCreateOrgRepo:        true,
   337  	}
   338  	if err = db.Insert(ctx, t); err != nil {
   339  		return fmt.Errorf("insert owner team: %w", err)
   340  	}
   341  
   342  	// insert units for team
   343  	units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes))
   344  	for _, tp := range unit.AllRepoUnitTypes {
   345  		up := perm.AccessModeOwner
   346  		if tp == unit.TypeExternalTracker || tp == unit.TypeExternalWiki {
   347  			up = perm.AccessModeRead
   348  		}
   349  		units = append(units, TeamUnit{
   350  			OrgID:      org.ID,
   351  			TeamID:     t.ID,
   352  			Type:       tp,
   353  			AccessMode: up,
   354  		})
   355  	}
   356  
   357  	if err = db.Insert(ctx, &units); err != nil {
   358  		return err
   359  	}
   360  
   361  	if err = db.Insert(ctx, &TeamUser{
   362  		UID:    owner.ID,
   363  		OrgID:  org.ID,
   364  		TeamID: t.ID,
   365  	}); err != nil {
   366  		return fmt.Errorf("insert team-user relation: %w", err)
   367  	}
   368  
   369  	return committer.Commit()
   370  }
   371  
   372  // GetOrgByName returns organization by given name.
   373  func GetOrgByName(ctx context.Context, name string) (*Organization, error) {
   374  	if len(name) == 0 {
   375  		return nil, ErrOrgNotExist{0, name}
   376  	}
   377  	u := &Organization{
   378  		LowerName: strings.ToLower(name),
   379  		Type:      user_model.UserTypeOrganization,
   380  	}
   381  	has, err := db.GetEngine(ctx).Get(u)
   382  	if err != nil {
   383  		return nil, err
   384  	} else if !has {
   385  		return nil, ErrOrgNotExist{0, name}
   386  	}
   387  	return u, nil
   388  }
   389  
   390  // DeleteOrganization deletes models associated to an organization.
   391  func DeleteOrganization(ctx context.Context, org *Organization) error {
   392  	if org.Type != user_model.UserTypeOrganization {
   393  		return fmt.Errorf("%s is a user not an organization", org.Name)
   394  	}
   395  
   396  	if err := db.DeleteBeans(ctx,
   397  		&Team{OrgID: org.ID},
   398  		&OrgUser{OrgID: org.ID},
   399  		&TeamUser{OrgID: org.ID},
   400  		&TeamUnit{OrgID: org.ID},
   401  		&TeamInvite{OrgID: org.ID},
   402  		&secret_model.Secret{OwnerID: org.ID},
   403  	); err != nil {
   404  		return fmt.Errorf("DeleteBeans: %w", err)
   405  	}
   406  
   407  	if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil {
   408  		return fmt.Errorf("Delete: %w", err)
   409  	}
   410  
   411  	return nil
   412  }
   413  
   414  // GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization
   415  func (org *Organization) GetOrgUserMaxAuthorizeLevel(uid int64) (perm.AccessMode, error) {
   416  	var authorize perm.AccessMode
   417  	_, err := db.GetEngine(db.DefaultContext).
   418  		Select("max(team.authorize)").
   419  		Table("team").
   420  		Join("INNER", "team_user", "team_user.team_id = team.id").
   421  		Where("team_user.uid = ?", uid).
   422  		And("team_user.org_id = ?", org.ID).
   423  		Get(&authorize)
   424  	return authorize, err
   425  }
   426  
   427  // GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization
   428  func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*user_model.User, error) {
   429  	// Use a map, in order to de-duplicate users.
   430  	users := make(map[int64]*user_model.User)
   431  	return users, db.GetEngine(ctx).
   432  		Join("INNER", "`team_user`", "`team_user`.uid=`user`.id").
   433  		Join("INNER", "`team`", "`team`.id=`team_user`.team_id").
   434  		Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})).
   435  		And("team_user.org_id = ?", orgID).Find(&users)
   436  }
   437  
   438  // SearchOrganizationsOptions options to filter organizations
   439  type SearchOrganizationsOptions struct {
   440  	db.ListOptions
   441  	All bool
   442  }
   443  
   444  // FindOrgOptions finds orgs options
   445  type FindOrgOptions struct {
   446  	db.ListOptions
   447  	UserID         int64
   448  	IncludePrivate bool
   449  }
   450  
   451  func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
   452  	cond := builder.Eq{"uid": userID}
   453  	if !includePrivate {
   454  		cond["is_public"] = true
   455  	}
   456  	return builder.Select("org_id").From("org_user").Where(cond)
   457  }
   458  
   459  func (opts FindOrgOptions) toConds() builder.Cond {
   460  	var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
   461  	if opts.UserID > 0 {
   462  		cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
   463  	}
   464  	if !opts.IncludePrivate {
   465  		cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
   466  	}
   467  	return cond
   468  }
   469  
   470  // FindOrgs returns a list of organizations according given conditions
   471  func FindOrgs(opts FindOrgOptions) ([]*Organization, error) {
   472  	orgs := make([]*Organization, 0, 10)
   473  	sess := db.GetEngine(db.DefaultContext).
   474  		Where(opts.toConds()).
   475  		Asc("`user`.name")
   476  	if opts.Page > 0 && opts.PageSize > 0 {
   477  		sess.Limit(opts.PageSize, opts.PageSize*(opts.Page-1))
   478  	}
   479  	return orgs, sess.Find(&orgs)
   480  }
   481  
   482  // CountOrgs returns total count organizations according options
   483  func CountOrgs(opts FindOrgOptions) (int64, error) {
   484  	return db.GetEngine(db.DefaultContext).
   485  		Where(opts.toConds()).
   486  		Count(new(Organization))
   487  }
   488  
   489  // HasOrgOrUserVisible tells if the given user can see the given org or user
   490  func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
   491  	// If user is nil, it's an anonymous user/request.
   492  	// The Ghost user is handled like an anonymous user.
   493  	if user == nil || user.IsGhost() {
   494  		return orgOrUser.Visibility == structs.VisibleTypePublic
   495  	}
   496  
   497  	if user.IsAdmin || orgOrUser.ID == user.ID {
   498  		return true
   499  	}
   500  
   501  	if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) {
   502  		return false
   503  	}
   504  	return true
   505  }
   506  
   507  // HasOrgsVisible tells if the given user can see at least one of the orgs provided
   508  func HasOrgsVisible(orgs []*Organization, user *user_model.User) bool {
   509  	if len(orgs) == 0 {
   510  		return false
   511  	}
   512  
   513  	for _, org := range orgs {
   514  		if HasOrgOrUserVisible(db.DefaultContext, org.AsUser(), user) {
   515  			return true
   516  		}
   517  	}
   518  	return false
   519  }
   520  
   521  // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
   522  // are allowed to create repos.
   523  func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {
   524  	orgs := make([]*Organization, 0, 10)
   525  
   526  	return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`").
   527  		Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id").
   528  		Join("INNER", "`team`", "`team`.id = `team_user`.team_id").
   529  		Where(builder.Eq{"`team_user`.uid": userID}).
   530  		And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))).
   531  		Asc("`user`.name").
   532  		Find(&orgs)
   533  }
   534  
   535  // GetOrgUsersByOrgID returns all organization-user relations by organization ID.
   536  func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
   537  	sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
   538  	if opts.PublicOnly {
   539  		sess.And("is_public = ?", true)
   540  	}
   541  	if opts.ListOptions.PageSize > 0 {
   542  		sess = db.SetSessionPagination(sess, opts)
   543  
   544  		ous := make([]*OrgUser, 0, opts.PageSize)
   545  		return ous, sess.Find(&ous)
   546  	}
   547  
   548  	var ous []*OrgUser
   549  	return ous, sess.Find(&ous)
   550  }
   551  
   552  // ChangeOrgUserStatus changes public or private membership status.
   553  func ChangeOrgUserStatus(orgID, uid int64, public bool) error {
   554  	ou := new(OrgUser)
   555  	has, err := db.GetEngine(db.DefaultContext).
   556  		Where("uid=?", uid).
   557  		And("org_id=?", orgID).
   558  		Get(ou)
   559  	if err != nil {
   560  		return err
   561  	} else if !has {
   562  		return nil
   563  	}
   564  
   565  	ou.IsPublic = public
   566  	_, err = db.GetEngine(db.DefaultContext).ID(ou.ID).Cols("is_public").Update(ou)
   567  	return err
   568  }
   569  
   570  // AddOrgUser adds new user to given organization.
   571  func AddOrgUser(orgID, uid int64) error {
   572  	isAlreadyMember, err := IsOrganizationMember(db.DefaultContext, orgID, uid)
   573  	if err != nil || isAlreadyMember {
   574  		return err
   575  	}
   576  
   577  	ctx, committer, err := db.TxContext(db.DefaultContext)
   578  	if err != nil {
   579  		return err
   580  	}
   581  	defer committer.Close()
   582  
   583  	// check in transaction
   584  	isAlreadyMember, err = IsOrganizationMember(ctx, orgID, uid)
   585  	if err != nil || isAlreadyMember {
   586  		return err
   587  	}
   588  
   589  	ou := &OrgUser{
   590  		UID:      uid,
   591  		OrgID:    orgID,
   592  		IsPublic: setting.Service.DefaultOrgMemberVisible,
   593  	}
   594  
   595  	if err := db.Insert(ctx, ou); err != nil {
   596  		return err
   597  	} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil {
   598  		return err
   599  	}
   600  
   601  	return committer.Commit()
   602  }
   603  
   604  // GetOrgByID returns the user object by given ID if exists.
   605  func GetOrgByID(ctx context.Context, id int64) (*Organization, error) {
   606  	u := new(Organization)
   607  	has, err := db.GetEngine(ctx).ID(id).Get(u)
   608  	if err != nil {
   609  		return nil, err
   610  	} else if !has {
   611  		return nil, user_model.ErrUserNotExist{
   612  			UID:   id,
   613  			Name:  "",
   614  			KeyID: 0,
   615  		}
   616  	}
   617  	return u, nil
   618  }
   619  
   620  // RemoveOrgRepo removes all team-repository relations of organization.
   621  func RemoveOrgRepo(ctx context.Context, orgID, repoID int64) error {
   622  	teamRepos := make([]*TeamRepo, 0, 10)
   623  	e := db.GetEngine(ctx)
   624  	if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil {
   625  		return err
   626  	}
   627  
   628  	if len(teamRepos) == 0 {
   629  		return nil
   630  	}
   631  
   632  	if _, err := e.Delete(&TeamRepo{
   633  		OrgID:  orgID,
   634  		RepoID: repoID,
   635  	}); err != nil {
   636  		return err
   637  	}
   638  
   639  	teamIDs := make([]int64, len(teamRepos))
   640  	for i, teamRepo := range teamRepos {
   641  		teamIDs[i] = teamRepo.TeamID
   642  	}
   643  
   644  	_, err := e.Decr("num_repos").In("id", teamIDs).Update(new(Team))
   645  	return err
   646  }
   647  
   648  func (org *Organization) getUserTeams(ctx context.Context, userID int64, cols ...string) ([]*Team, error) {
   649  	teams := make([]*Team, 0, org.NumTeams)
   650  	return teams, db.GetEngine(ctx).
   651  		Where("`team_user`.org_id = ?", org.ID).
   652  		Join("INNER", "team_user", "`team_user`.team_id = team.id").
   653  		Join("INNER", "`user`", "`user`.id=team_user.uid").
   654  		And("`team_user`.uid = ?", userID).
   655  		Asc("`user`.name").
   656  		Cols(cols...).
   657  		Find(&teams)
   658  }
   659  
   660  func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) {
   661  	teamIDs := make([]int64, 0, org.NumTeams)
   662  	return teamIDs, db.GetEngine(ctx).
   663  		Table("team").
   664  		Cols("team.id").
   665  		Where("`team_user`.org_id = ?", org.ID).
   666  		Join("INNER", "team_user", "`team_user`.team_id = team.id").
   667  		And("`team_user`.uid = ?", userID).
   668  		Find(&teamIDs)
   669  }
   670  
   671  // TeamsWithAccessToRepo returns all teams that have given access level to the repository.
   672  func (org *Organization) TeamsWithAccessToRepo(repoID int64, mode perm.AccessMode) ([]*Team, error) {
   673  	return GetTeamsWithAccessToRepo(db.DefaultContext, org.ID, repoID, mode)
   674  }
   675  
   676  // GetUserTeamIDs returns of all team IDs of the organization that user is member of.
   677  func (org *Organization) GetUserTeamIDs(userID int64) ([]int64, error) {
   678  	return org.getUserTeamIDs(db.DefaultContext, userID)
   679  }
   680  
   681  // GetUserTeams returns all teams that belong to user,
   682  // and that the user has joined.
   683  func (org *Organization) GetUserTeams(userID int64) ([]*Team, error) {
   684  	return org.getUserTeams(db.DefaultContext, userID)
   685  }
   686  
   687  // AccessibleReposEnvironment operations involving the repositories that are
   688  // accessible to a particular user
   689  type AccessibleReposEnvironment interface {
   690  	CountRepos() (int64, error)
   691  	RepoIDs(page, pageSize int) ([]int64, error)
   692  	Repos(page, pageSize int) (repo_model.RepositoryList, error)
   693  	MirrorRepos() (repo_model.RepositoryList, error)
   694  	AddKeyword(keyword string)
   695  	SetSort(db.SearchOrderBy)
   696  }
   697  
   698  type accessibleReposEnv struct {
   699  	org     *Organization
   700  	user    *user_model.User
   701  	team    *Team
   702  	teamIDs []int64
   703  	ctx     context.Context
   704  	keyword string
   705  	orderBy db.SearchOrderBy
   706  }
   707  
   708  // AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org`
   709  // that are accessible to the specified user.
   710  func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (AccessibleReposEnvironment, error) {
   711  	var user *user_model.User
   712  
   713  	if userID > 0 {
   714  		u, err := user_model.GetUserByID(ctx, userID)
   715  		if err != nil {
   716  			return nil, err
   717  		}
   718  		user = u
   719  	}
   720  
   721  	teamIDs, err := org.getUserTeamIDs(ctx, userID)
   722  	if err != nil {
   723  		return nil, err
   724  	}
   725  	return &accessibleReposEnv{
   726  		org:     org,
   727  		user:    user,
   728  		teamIDs: teamIDs,
   729  		ctx:     ctx,
   730  		orderBy: db.SearchOrderByRecentUpdated,
   731  	}, nil
   732  }
   733  
   734  // AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org`
   735  // that are accessible to the specified team.
   736  func (org *Organization) AccessibleTeamReposEnv(team *Team) AccessibleReposEnvironment {
   737  	return &accessibleReposEnv{
   738  		org:     org,
   739  		team:    team,
   740  		ctx:     db.DefaultContext,
   741  		orderBy: db.SearchOrderByRecentUpdated,
   742  	}
   743  }
   744  
   745  func (env *accessibleReposEnv) cond() builder.Cond {
   746  	cond := builder.NewCond()
   747  	if env.team != nil {
   748  		cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID})
   749  	} else {
   750  		if env.user == nil || !env.user.IsRestricted {
   751  			cond = cond.Or(builder.Eq{
   752  				"`repository`.owner_id":   env.org.ID,
   753  				"`repository`.is_private": false,
   754  			})
   755  		}
   756  		if len(env.teamIDs) > 0 {
   757  			cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs))
   758  		}
   759  	}
   760  	if env.keyword != "" {
   761  		cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)})
   762  	}
   763  	return cond
   764  }
   765  
   766  func (env *accessibleReposEnv) CountRepos() (int64, error) {
   767  	repoCount, err := db.GetEngine(env.ctx).
   768  		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
   769  		Where(env.cond()).
   770  		Distinct("`repository`.id").
   771  		Count(&repo_model.Repository{})
   772  	if err != nil {
   773  		return 0, fmt.Errorf("count user repositories in organization: %w", err)
   774  	}
   775  	return repoCount, nil
   776  }
   777  
   778  func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) {
   779  	if page <= 0 {
   780  		page = 1
   781  	}
   782  
   783  	repoIDs := make([]int64, 0, pageSize)
   784  	return repoIDs, db.GetEngine(env.ctx).
   785  		Table("repository").
   786  		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
   787  		Where(env.cond()).
   788  		GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]).
   789  		OrderBy(string(env.orderBy)).
   790  		Limit(pageSize, (page-1)*pageSize).
   791  		Cols("`repository`.id").
   792  		Find(&repoIDs)
   793  }
   794  
   795  func (env *accessibleReposEnv) Repos(page, pageSize int) (repo_model.RepositoryList, error) {
   796  	repoIDs, err := env.RepoIDs(page, pageSize)
   797  	if err != nil {
   798  		return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
   799  	}
   800  
   801  	repos := make([]*repo_model.Repository, 0, len(repoIDs))
   802  	if len(repoIDs) == 0 {
   803  		return repos, nil
   804  	}
   805  
   806  	return repos, db.GetEngine(env.ctx).
   807  		In("`repository`.id", repoIDs).
   808  		OrderBy(string(env.orderBy)).
   809  		Find(&repos)
   810  }
   811  
   812  func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) {
   813  	repoIDs := make([]int64, 0, 10)
   814  	return repoIDs, db.GetEngine(env.ctx).
   815  		Table("repository").
   816  		Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true).
   817  		Where(env.cond()).
   818  		GroupBy("`repository`.id, `repository`.updated_unix").
   819  		OrderBy(string(env.orderBy)).
   820  		Cols("`repository`.id").
   821  		Find(&repoIDs)
   822  }
   823  
   824  func (env *accessibleReposEnv) MirrorRepos() (repo_model.RepositoryList, error) {
   825  	repoIDs, err := env.MirrorRepoIDs()
   826  	if err != nil {
   827  		return nil, fmt.Errorf("MirrorRepoIDs: %w", err)
   828  	}
   829  
   830  	repos := make([]*repo_model.Repository, 0, len(repoIDs))
   831  	if len(repoIDs) == 0 {
   832  		return repos, nil
   833  	}
   834  
   835  	return repos, db.GetEngine(env.ctx).
   836  		In("`repository`.id", repoIDs).
   837  		Find(&repos)
   838  }
   839  
   840  func (env *accessibleReposEnv) AddKeyword(keyword string) {
   841  	env.keyword = keyword
   842  }
   843  
   844  func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) {
   845  	env.orderBy = orderBy
   846  }