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 }