@@ -3,6 +3,7 @@ package providers
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "io/ioutil"
89 "net/http"
@@ -23,6 +24,7 @@ type GitHubProvider struct {
2324 Team string
2425 Repo string
2526 Token string
27+ Users []string
2628}
2729
2830var _ Provider = (*GitHubProvider)(nil)
@@ -80,6 +82,11 @@ func (p *GitHubProvider) SetRepo(repo, token string) {
8082 p.Token = token
8183}
8284
85+// SetUsers configures allowed usernames
86+func (p *GitHubProvider) SetUsers(users []string) {
87+ p.Users = users
88+}
89+
8390func (p *GitHubProvider) hasOrg(ctx context.Context, accessToken string) (bool, error) {
8491 // https://developer.github.com/v3/orgs/#list-your-organizations
8592
@@ -317,6 +324,46 @@ func (p *GitHubProvider) hasRepo(ctx context.Context, accessToken string) (bool,
317324 return repo.Permissions.Push || (repo.Private && repo.Permissions.Pull), nil
318325}
319326
327+func (p *GitHubProvider) hasUser(ctx context.Context, accessToken string) (bool, error) {
328+ // https://developer.github.com/v3/users/#get-the-authenticated-user
329+
330+ var user struct {
331+ Login string `json:"login"`
332+ Email string `json:"email"`
333+ }
334+
335+ endpoint := &url.URL{
336+ Scheme: p.ValidateURL.Scheme,
337+ Host: p.ValidateURL.Host,
338+ Path: path.Join(p.ValidateURL.Path, "/user"),
339+ }
340+ req, _ := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil)
341+ req.Header = getGitHubHeader(accessToken)
342+ resp, err := http.DefaultClient.Do(req)
343+ if err != nil {
344+ return false, err
345+ }
346+ defer resp.Body.Close()
347+
348+ body, err := ioutil.ReadAll(resp.Body)
349+ if err != nil {
350+ return false, err
351+ }
352+ if resp.StatusCode != 200 {
353+ return false, fmt.Errorf("got %d from %q %s",
354+ resp.StatusCode, stripToken(endpoint.String()), body)
355+ }
356+
357+ if err := json.Unmarshal(body, &user); err != nil {
358+ return false, err
359+ }
360+
361+ if p.isVerifiedUser(user.Login) {
362+ return true, nil
363+ }
364+ return false, nil
365+}
366+
320367func (p *GitHubProvider) isCollaborator(ctx context.Context, username, accessToken string) (bool, error) {
321368 //https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
322369
@@ -356,21 +403,36 @@ func (p *GitHubProvider) GetEmailAddress(ctx context.Context, s *sessions.Sessio
356403 Verified bool `json:"verified"`
357404 }
358405
359− // if we require an Org or Team, check that first
360− if p.Org != "" {
361− if p.Team != "" {
362− if ok, err := p.hasOrgAndTeam(ctx, s.AccessToken); err != nil || !ok {
363− return "", err
406+ // If usernames are set, check that first
407+ verifiedUser := false
408+ if len(p.Users) > 0 {
409+ var err error
410+ verifiedUser, err = p.hasUser(ctx, s.AccessToken)
411+ if err != nil {
412+ return "", err
413+ }
414+ // org and repository options are not configured
415+ if !verifiedUser && p.Org == "" && p.Repo == "" {
416+ return "", errors.New("missing github user")
417+ }
418+ }
419+ // If a user is verified by username options, skip the following restrictions
420+ if !verifiedUser {
421+ if p.Org != "" {
422+ if p.Team != "" {
423+ if ok, err := p.hasOrgAndTeam(ctx, s.AccessToken); err != nil || !ok {
424+ return "", err
425+ }
426+ } else {
427+ if ok, err := p.hasOrg(ctx, s.AccessToken); err != nil || !ok {
428+ return "", err
429+ }
364430 }
365− } else {
366− if ok, err := p.hasOrg(ctx, s.AccessToken); err != nil || !ok {
431+ } else if p.Repo != "" && p.Token == "" { // If we have a token we'll do the collaborator check in GetUserName
432+ if ok, err := p.hasRepo(ctx, s.AccessToken); err != nil || !ok {
367433 return "", err
368434 }
369435 }
370− } else if p.Repo != "" && p.Token == "" { // If we have a token we'll do the collaborator check in GetUserName
371− if ok, err := p.hasRepo(ctx, s.AccessToken); err != nil || !ok {
372− return "", err
373− }
374436 }
375437
376438 endpoint := &url.URL{
@@ -456,7 +518,7 @@ func (p *GitHubProvider) GetUserName(ctx context.Context, s *sessions.SessionSta
456518 }
457519
458520 // Now that we have the username we can check collaborator status
459− if p.Org == "" && p.Repo != "" && p.Token != "" {
521+ if !p.isVerifiedUser(user.Login) && p.Org == "" && p.Repo != "" && p.Token != "" {
460522 if ok, err := p.isCollaborator(ctx, user.Login, p.Token); err != nil || !ok {
461523 return "", err
462524 }
@@ -469,3 +531,13 @@ func (p *GitHubProvider) GetUserName(ctx context.Context, s *sessions.SessionSta
469531func (p *GitHubProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool {
470532 return validateToken(ctx, p, s.AccessToken, getGitHubHeader(s.AccessToken))
471533}
534+
535+// isVerifiedUser
536+func (p *GitHubProvider) isVerifiedUser(username string) bool {
537+ for _, u := range p.Users {
538+ if username == u {
539+ return true
540+ }
541+ }
542+ return false
543+}