Skip to content

Commit c91b87f

Browse files
nobuyonabokihms
andauthored
Add preferredEmailDomain config option for GitHub connector (#2740)
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>
1 parent 263526a commit c91b87f

File tree

2 files changed

+366
-20
lines changed

2 files changed

+366
-20
lines changed

connector/github/github.go

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ var (
3939

4040
// Config holds configuration options for github logins.
4141
type Config struct {
42-
ClientID string `json:"clientID"`
43-
ClientSecret string `json:"clientSecret"`
44-
RedirectURI string `json:"redirectURI"`
45-
Org string `json:"org"`
46-
Orgs []Org `json:"orgs"`
47-
HostName string `json:"hostName"`
48-
RootCA string `json:"rootCA"`
49-
TeamNameField string `json:"teamNameField"`
50-
LoadAllGroups bool `json:"loadAllGroups"`
51-
UseLoginAsID bool `json:"useLoginAsID"`
42+
ClientID string `json:"clientID"`
43+
ClientSecret string `json:"clientSecret"`
44+
RedirectURI string `json:"redirectURI"`
45+
Org string `json:"org"`
46+
Orgs []Org `json:"orgs"`
47+
HostName string `json:"hostName"`
48+
RootCA string `json:"rootCA"`
49+
TeamNameField string `json:"teamNameField"`
50+
LoadAllGroups bool `json:"loadAllGroups"`
51+
UseLoginAsID bool `json:"useLoginAsID"`
52+
PreferredEmailDomain string `json:"preferredEmailDomain"`
5253
}
5354

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

7778
g := githubConnector{
78-
redirectURI: c.RedirectURI,
79-
org: c.Org,
80-
orgs: c.Orgs,
81-
clientID: c.ClientID,
82-
clientSecret: c.ClientSecret,
83-
apiURL: apiURL,
84-
logger: logger,
85-
useLoginAsID: c.UseLoginAsID,
79+
redirectURI: c.RedirectURI,
80+
org: c.Org,
81+
orgs: c.Orgs,
82+
clientID: c.ClientID,
83+
clientSecret: c.ClientSecret,
84+
apiURL: apiURL,
85+
logger: logger,
86+
useLoginAsID: c.UseLoginAsID,
87+
preferredEmailDomain: c.PreferredEmailDomain,
8688
}
8789

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

120+
if c.PreferredEmailDomain != "" {
121+
if strings.HasSuffix(c.PreferredEmailDomain, "*") {
122+
return nil, errors.New("invalid PreferredEmailDomain: glob pattern cannot end with \"*\"")
123+
}
124+
}
125+
118126
return &g, nil
119127
}
120128

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

154164
// groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex
@@ -548,7 +558,13 @@ type userEmail struct {
548558
// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,
549559
// which inserts a bearer token as part of the request.
550560
func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (string, error) {
561+
var (
562+
primaryEmail userEmail
563+
preferredEmails []userEmail
564+
)
565+
551566
apiURL := c.apiURL + "/user/emails"
567+
552568
for {
553569
// https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
554570
var (
@@ -575,7 +591,17 @@ func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (s
575591
}
576592

577593
if email.Verified && email.Primary {
578-
return email.Email, nil
594+
primaryEmail = email
595+
}
596+
597+
if c.preferredEmailDomain != "" {
598+
_, domainPart, ok := strings.Cut(email.Email, "@")
599+
if !ok {
600+
return "", errors.New("github: invalid format email is detected")
601+
}
602+
if email.Verified && c.isPreferredEmailDomain(domainPart) {
603+
preferredEmails = append(preferredEmails, email)
604+
}
579605
}
580606
}
581607

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

587-
return "", errors.New("github: user has no verified, primary email")
613+
if len(preferredEmails) > 0 {
614+
return preferredEmails[0].Email, nil
615+
}
616+
617+
if primaryEmail.Email != "" {
618+
return primaryEmail.Email, nil
619+
}
620+
621+
return "", errors.New("github: user has no verified, primary email or preferred-domain email")
622+
}
623+
624+
// isPreferredEmailDomain checks the domain is matching with preferredEmailDomain.
625+
func (c *githubConnector) isPreferredEmailDomain(domain string) bool {
626+
if domain == c.preferredEmailDomain {
627+
return true
628+
}
629+
630+
preferredDomainParts := strings.Split(c.preferredEmailDomain, ".")
631+
domainParts := strings.Split(domain, ".")
632+
633+
if len(preferredDomainParts) != len(domainParts) {
634+
return false
635+
}
636+
637+
for i, v := range preferredDomainParts {
638+
if domainParts[i] != v && v != "*" {
639+
return false
640+
}
641+
}
642+
return true
588643
}
589644

590645
// userInOrg queries the GitHub API for a users' org membership.

0 commit comments

Comments
 (0)