@@ -48,6 +48,7 @@ type ValidOwner struct {
48
48
orgName string
49
49
orgTeams []* github.Team
50
50
orgRepoName string
51
+ outsideCollaborators map [string ]struct {}
51
52
ignOwners map [string ]struct {}
52
53
allowUnownedPatterns bool
53
54
ownersMustBeTeams bool
@@ -297,6 +298,12 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
297
298
}
298
299
}
299
300
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
+
300
307
userName := strings .TrimPrefix (name , "@" )
301
308
_ , _ , err := v .ghClient .Users .Get (ctx , userName )
302
309
if err != nil { // TODO(mszostok): implement retry?
@@ -314,15 +321,18 @@ func (v *ValidOwner) validateGitHubUser(ctx context.Context, name string) *valid
314
321
}
315
322
316
323
_ , 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 ("The user %q is neither a collaborator nor a member of this repository." , name )
319
327
}
320
328
321
329
return nil
322
330
}
323
331
324
332
// 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
+ //
326
336
// But latency is too huge for checking each single user independent
327
337
// better and faster is to ask for all members and cache them.
328
338
func (v * ValidOwner ) initOrgListMembers (ctx context.Context ) error {
@@ -351,6 +361,36 @@ func (v *ValidOwner) initOrgListMembers(ctx context.Context) error {
351
361
return nil
352
362
}
353
363
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
+
354
394
// Name returns human-readable name of the validator
355
395
func (ValidOwner ) Name () string {
356
396
return "Valid Owner Checker"
0 commit comments