Skip to content

Commit

Permalink
Add preferredEmailDomain config option for GitHub connector (#2740)
Browse files Browse the repository at this point in the history
Signed-off-by: nobuyo <longzechangsheng@gmail.com>
Signed-off-by: Nobuo Takizawa <nobuyo@users.noreply.github.com>
Co-authored-by: Maksim Nabokikh <max.nabokih@gmail.com>
  • Loading branch information
nobuyo and nabokihms authored Dec 22, 2022
1 parent 263526a commit c91b87f
Show file tree
Hide file tree
Showing 2 changed files with 366 additions and 20 deletions.
95 changes: 75 additions & 20 deletions connector/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ var (

// Config holds configuration options for github logins.
type Config struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Org string `json:"org"`
Orgs []Org `json:"orgs"`
HostName string `json:"hostName"`
RootCA string `json:"rootCA"`
TeamNameField string `json:"teamNameField"`
LoadAllGroups bool `json:"loadAllGroups"`
UseLoginAsID bool `json:"useLoginAsID"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURI string `json:"redirectURI"`
Org string `json:"org"`
Orgs []Org `json:"orgs"`
HostName string `json:"hostName"`
RootCA string `json:"rootCA"`
TeamNameField string `json:"teamNameField"`
LoadAllGroups bool `json:"loadAllGroups"`
UseLoginAsID bool `json:"useLoginAsID"`
PreferredEmailDomain string `json:"preferredEmailDomain"`
}

// Org holds org-team filters, in which teams are optional.
Expand All @@ -75,14 +76,15 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
}

g := githubConnector{
redirectURI: c.RedirectURI,
org: c.Org,
orgs: c.Orgs,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
apiURL: apiURL,
logger: logger,
useLoginAsID: c.UseLoginAsID,
redirectURI: c.RedirectURI,
org: c.Org,
orgs: c.Orgs,
clientID: c.ClientID,
clientSecret: c.ClientSecret,
apiURL: apiURL,
logger: logger,
useLoginAsID: c.UseLoginAsID,
preferredEmailDomain: c.PreferredEmailDomain,
}

if c.HostName != "" {
Expand Down Expand Up @@ -115,6 +117,12 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
return nil, fmt.Errorf("invalid connector config: unsupported team name field value `%s`", c.TeamNameField)
}

if c.PreferredEmailDomain != "" {
if strings.HasSuffix(c.PreferredEmailDomain, "*") {
return nil, errors.New("invalid PreferredEmailDomain: glob pattern cannot end with \"*\"")
}
}

return &g, nil
}

Expand Down Expand Up @@ -149,6 +157,8 @@ type githubConnector struct {
loadAllGroups bool
// if set to true will use the user's handle rather than their numeric id as the ID
useLoginAsID bool
// the domain to be preferred among the user's emails. e.g. "github.com"
preferredEmailDomain string
}

// groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex
Expand Down Expand Up @@ -548,7 +558,13 @@ type userEmail struct {
// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,
// which inserts a bearer token as part of the request.
func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (string, error) {
var (
primaryEmail userEmail
preferredEmails []userEmail
)

apiURL := c.apiURL + "/user/emails"

for {
// https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
var (
Expand All @@ -575,7 +591,17 @@ func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (s
}

if email.Verified && email.Primary {
return email.Email, nil
primaryEmail = email
}

if c.preferredEmailDomain != "" {
_, domainPart, ok := strings.Cut(email.Email, "@")
if !ok {
return "", errors.New("github: invalid format email is detected")
}
if email.Verified && c.isPreferredEmailDomain(domainPart) {
preferredEmails = append(preferredEmails, email)
}
}
}

Expand All @@ -584,7 +610,36 @@ func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (s
}
}

return "", errors.New("github: user has no verified, primary email")
if len(preferredEmails) > 0 {
return preferredEmails[0].Email, nil
}

if primaryEmail.Email != "" {
return primaryEmail.Email, nil
}

return "", errors.New("github: user has no verified, primary email or preferred-domain email")
}

// isPreferredEmailDomain checks the domain is matching with preferredEmailDomain.
func (c *githubConnector) isPreferredEmailDomain(domain string) bool {
if domain == c.preferredEmailDomain {
return true
}

preferredDomainParts := strings.Split(c.preferredEmailDomain, ".")
domainParts := strings.Split(domain, ".")

if len(preferredDomainParts) != len(domainParts) {
return false
}

for i, v := range preferredDomainParts {
if domainParts[i] != v && v != "*" {
return false
}
}
return true
}

// userInOrg queries the GitHub API for a users' org membership.
Expand Down
Loading

0 comments on commit c91b87f

Please sign in to comment.