@@ -3,6 +3,7 @@ package providers
5 "encoding/json"
5 "encoding/json"
7 "io/ioutil"
8 "io/ioutil"
@@ -23,6 +24,7 @@ type GitHubProvider struct {
23 Team string
24 Team string
24 Repo string
25 Repo string
25 Token string
26 Token string
28var _ Provider = (*GitHubProvider)(nil)
30var _ Provider = (*GitHubProvider)(nil)
@@ -80,6 +82,11 @@ func (p *GitHubProvider) SetRepo(repo, token string) {
80 p.Token = token
82 p.Token = token
85+// SetUsers configures allowed usernames
86+func (p *GitHubProvider) SetUsers(users []string) {
83func (p *GitHubProvider) hasOrg(ctx context.Context, accessToken string) (bool, error) {
90func (p *GitHubProvider) hasOrg(ctx context.Context, accessToken string) (bool, error) {
84 // https://developer.github.com/v3/orgs/#list-your-organizations
91 // https://developer.github.com/v3/orgs/#list-your-organizations
@@ -317,6 +324,46 @@ func (p *GitHubProvider) hasRepo(ctx context.Context, accessToken string) (bool,
317 return repo.Permissions.Push || (repo.Private && repo.Permissions.Pull), nil
324 return repo.Permissions.Push || (repo.Private && repo.Permissions.Pull), nil
327+func (p *GitHubProvider) hasUser(ctx context.Context, accessToken string) (bool, error) {
328+ // https://developer.github.com/v3/users/#get-the-authenticated-user
331+ Login string `json:"login"`
332+ Email string `json:"email"`
335+ endpoint := &url.URL{
336+ Scheme: p.ValidateURL.Scheme,
337+ Host: p.ValidateURL.Host,
338+ Path: path.Join(p.ValidateURL.Path, "/user"),
340+ req, _ := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil)
341+ req.Header = getGitHubHeader(accessToken)
342+ resp, err := http.DefaultClient.Do(req)
346+ defer resp.Body.Close()
348+ body, err := ioutil.ReadAll(resp.Body)
352+ if resp.StatusCode != 200 {
353+ return false, fmt.Errorf("got %d from %q %s",
354+ resp.StatusCode, stripToken(endpoint.String()), body)
357+ if err := json.Unmarshal(body, &user); err != nil {
361+ if p.isVerifiedUser(user.Login) {
320func (p *GitHubProvider) isCollaborator(ctx context.Context, username, accessToken string) (bool, error) {
367func (p *GitHubProvider) isCollaborator(ctx context.Context, username, accessToken string) (bool, error) {
321 //https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
368 //https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
@@ -356,21 +403,36 @@ func (p *GitHubProvider) GetEmailAddress(ctx context.Context, s *sessions.Sessio
356 Verified bool `json:"verified"`
403 Verified bool `json:"verified"`
359− // if we require an Org or Team, check that first
406+ // If usernames are set, check that first
407+ verifiedUser := false
408+ if len(p.Users) > 0 {
410+ verifiedUser, err = p.hasUser(ctx, s.AccessToken)
414+ // org and repository options are not configured
415+ if !verifiedUser && p.Org == "" && p.Repo == "" {
416+ return "", errors.New("missing github user")
419+ // If a user is verified by username options, skip the following restrictions
360− if p.Org != "" {
421+ if p.Org != "" {
361− if p.Team != "" {
422+ if p.Team != "" {
362− if ok, err := p.hasOrgAndTeam(ctx, s.AccessToken); err != nil || !ok {
423+ if ok, err := p.hasOrgAndTeam(ctx, s.AccessToken); err != nil || !ok {
427+ if ok, err := p.hasOrg(ctx, s.AccessToken); err != nil || !ok {
363− return "", err
428+ return "", err
365− } else {
431+ } else if p.Repo != "" && p.Token == "" { // If we have a token we'll do the collaborator check in GetUserName
366− if ok, err := p.hasOrg(ctx, s.AccessToken); err != nil || !ok {
432+ if ok, err := p.hasRepo(ctx, s.AccessToken); err != nil || !ok {
367 return "", err
433 return "", err
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 {
376 endpoint := &url.URL{
438 endpoint := &url.URL{
@@ -456,7 +518,7 @@ func (p *GitHubProvider) GetUserName(ctx context.Context, s *sessions.SessionSta
458 // Now that we have the username we can check collaborator status
520 // 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 != "" {
460 if ok, err := p.isCollaborator(ctx, user.Login, p.Token); err != nil || !ok {
522 if ok, err := p.isCollaborator(ctx, user.Login, p.Token); err != nil || !ok {
461 return "", err
523 return "", err
@@ -469,3 +531,13 @@ func (p *GitHubProvider) GetUserName(ctx context.Context, s *sessions.SessionSta
469func (p *GitHubProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool {
531func (p *GitHubProvider) ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool {
470 return validateToken(ctx, p, s.AccessToken, getGitHubHeader(s.AccessToken))
532 return validateToken(ctx, p, s.AccessToken, getGitHubHeader(s.AccessToken))
536+func (p *GitHubProvider) isVerifiedUser(username string) bool {
537+ for _, u := range p.Users {