@@ -48,6 +48,7 @@ type ValidOwner struct {
4848 orgName string
4949 orgTeams []* github.Team
5050 orgRepoName string
51+ outsideCollaborators * map [string ]struct {}
5152 ignOwners map [string ]struct {}
5253 allowUnownedPatterns bool
5354 ownersMustBeTeams bool
@@ -297,6 +298,12 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
297298 }
298299 }
299300
301+ if v .outsideCollaborators == nil { // TODO(mszostok): lazy init, make it more robust.
302+ if err := v .initOutsideCollaboratorsList (ctx ); err != nil {
303+ return newValidateError ("Cannot initialize outside collaborators list: %v" , err ).AsPermanent ()
304+ }
305+ }
306+
300307 userName := strings .TrimPrefix (name , "@" )
301308 _ , _ , err := v .ghClient .Users .Get (ctx , userName )
302309 if err != nil { // TODO(mszostok): implement retry?
@@ -314,15 +321,18 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
314321 }
315322
316323 _ , isMember := (* v .orgMembers )[userName ]
317- if ! isMember {
318- return newValidateError ("User %q is not a member of the organization" , name )
324+ _ , isOutsideCollaborator := (* v .outsideCollaborators )[userName ]
325+ if ! (isMember || isOutsideCollaborator ) {
326+ return newValidateError ("User %q is not an owner of the repository" , name )
319327 }
320328
321329 return nil
322330}
323331
324332// There is a method to check if user is a org member
325- // client.Organizations.IsMember(context.Background(), "org-name", "user-name")
333+ //
334+ // client.Organizations.IsMember(context.Background(), "org-name", "user-name")
335+ //
326336// But latency is too huge for checking each single user independent
327337// better and faster is to ask for all members and cache them.
328338func (v * ValidOwner ) initOrgListMembers (ctx context.Context ) error {
@@ -351,6 +361,36 @@ func (v *ValidOwner) initOrgListMembers(ctx context.Context) error {
351361 return nil
352362}
353363
364+ // Add all outside collaborators who are part of the repository to
365+ //
366+ // outsideCollaborators *map[string]struct{}
367+ func (v * ValidOwner ) initOutsideCollaboratorsList (ctx context.Context ) error {
368+ opt := & github.ListCollaboratorsOptions {
369+ ListOptions : github.ListOptions {PerPage : 100 },
370+ Affiliation : "outside" ,
371+ }
372+
373+ var allMembers []* github.User
374+ for {
375+ collaborators , resp , err := v .ghClient .Repositories .ListCollaborators (ctx , v .orgName , v .orgRepoName , opt )
376+ if err != nil {
377+ return err
378+ }
379+ allMembers = append (allMembers , collaborators ... )
380+ if resp .NextPage == 0 {
381+ break
382+ }
383+ opt .Page = resp .NextPage
384+ }
385+
386+ v .outsideCollaborators = & map [string ]struct {}{}
387+ for _ , u := range allMembers {
388+ (* v .outsideCollaborators )[u .GetLogin ()] = struct {}{}
389+ }
390+
391+ return nil
392+ }
393+
354394// Name returns human-readable name of the validator
355395func (ValidOwner ) Name () string {
356396 return "Valid Owner Checker"
0 commit comments