@@ -39,16 +39,17 @@ var (
39
39
40
40
// Config holds configuration options for github logins.
41
41
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"`
52
53
}
53
54
54
55
// 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)
75
76
}
76
77
77
78
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 ,
86
88
}
87
89
88
90
if c .HostName != "" {
@@ -115,6 +117,12 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
115
117
return nil , fmt .Errorf ("invalid connector config: unsupported team name field value `%s`" , c .TeamNameField )
116
118
}
117
119
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
+
118
126
return & g , nil
119
127
}
120
128
@@ -149,6 +157,8 @@ type githubConnector struct {
149
157
loadAllGroups bool
150
158
// if set to true will use the user's handle rather than their numeric id as the ID
151
159
useLoginAsID bool
160
+ // the domain to be preferred among the user's emails. e.g. "github.com"
161
+ preferredEmailDomain string
152
162
}
153
163
154
164
// groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex
@@ -548,7 +558,13 @@ type userEmail struct {
548
558
// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,
549
559
// which inserts a bearer token as part of the request.
550
560
func (c * githubConnector ) userEmail (ctx context.Context , client * http.Client ) (string , error ) {
561
+ var (
562
+ primaryEmail userEmail
563
+ preferredEmails []userEmail
564
+ )
565
+
551
566
apiURL := c .apiURL + "/user/emails"
567
+
552
568
for {
553
569
// https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
554
570
var (
@@ -575,7 +591,17 @@ func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (s
575
591
}
576
592
577
593
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
+ }
579
605
}
580
606
}
581
607
@@ -584,7 +610,36 @@ func (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (s
584
610
}
585
611
}
586
612
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
588
643
}
589
644
590
645
// userInOrg queries the GitHub API for a users' org membership.
0 commit comments