Skip to content

Commit 793d598

Browse files
committed
Add email domain filtering
1 parent 7a4539f commit 793d598

File tree

6 files changed

+156
-10
lines changed

6 files changed

+156
-10
lines changed

src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java

Lines changed: 116 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ of this software and associated documentation files (the "Software"), to deal
9191
import java.util.HashSet;
9292
import java.util.Set;
9393
import java.util.logging.Logger;
94+
import java.util.logging.Level;
9495
import javax.annotation.Nonnull;
9596
import javax.annotation.Nullable;
9697

@@ -107,12 +108,15 @@ public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm impl
107108
private static final String DEFAULT_API_URI = "https://api.github.com";
108109
private static final String DEFAULT_ENTERPRISE_API_SUFFIX = "/api/v3";
109110
private static final String DEFAULT_OAUTH_SCOPES = "read:org,user:email,repo";
111+
private static final Boolean DEFAULT_FORCE_GITHUB_EMAIL = false;
110112

111113
private String githubWebUri;
112114
private String githubApiUri;
113115
private String clientID;
114116
private Secret clientSecret;
115117
private String oauthScopes;
118+
private String emailDomains;
119+
private Boolean forceGithubEmail;
116120
private String[] myScopes;
117121

118122
/**
@@ -123,20 +127,39 @@ public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm impl
123127
* @param clientID The client ID for the created OAuth Application.
124128
* @param clientSecret The client secret for the created GitHub OAuth Application.
125129
* @param oauthScopes A comma separated list of OAuth Scopes to request access to.
130+
* @param emailDomains An optional comma separated list of domain(s) to select for email
131+
* @param forceGithubEmail Force the email from github to override the one in the profile
126132
*/
127133
@DataBoundConstructor
128134
public GithubSecurityRealm(String githubWebUri,
129135
String githubApiUri,
130136
String clientID,
131137
String clientSecret,
132-
String oauthScopes) {
138+
String oauthScopes,
139+
String emailDomains,
140+
Boolean forceGithubEmail) {
133141
super();
134142

135143
this.githubWebUri = Util.fixEmptyAndTrim(githubWebUri);
136144
this.githubApiUri = Util.fixEmptyAndTrim(githubApiUri);
137145
this.clientID = Util.fixEmptyAndTrim(clientID);
138146
setClientSecret(Util.fixEmptyAndTrim(clientSecret));
139147
this.oauthScopes = Util.fixEmptyAndTrim(oauthScopes);
148+
this.emailDomains = emailDomains.trim();
149+
this.forceGithubEmail = forceGithubEmail;
150+
}
151+
152+
/**
153+
This method is deprecated.
154+
@deprecated use GithubSecurityRealm(githubWebUri, githubApiUri, clientID, clientSecret, oauthScopes, emailDomains, forceGithubEmail)
155+
*/
156+
@Deprecated
157+
public GithubSecurityRealm(String githubWebUri,
158+
String githubApiUri,
159+
String clientID,
160+
String clientSecret,
161+
String oauthScopes) {
162+
this(githubWebUri, githubApiUri, clientID, clientSecret, oauthScopes, "", false);
140163
}
141164

142165
private GithubSecurityRealm() { }
@@ -186,6 +209,20 @@ private void setOauthScopes(String oauthScopes) {
186209
this.oauthScopes = oauthScopes;
187210
}
188211

212+
/**
213+
* @param emailDomains the emailDomains to set
214+
*/
215+
private void setEmailDomains(String emailDomains) {
216+
this.emailDomains = emailDomains;
217+
}
218+
219+
/**
220+
* @param forceGithubEmail the forceGithubEmail to set
221+
*/
222+
private void setForceGithubEmail(Boolean forceGithubEmail) {
223+
this.forceGithubEmail = forceGithubEmail;
224+
}
225+
189226
/**
190227
* Checks the security realm for a GitHub OAuth scope.
191228
* @param scope A scope to check for in the security realm.
@@ -244,6 +281,20 @@ public void marshal(Object source, HierarchicalStreamWriter writer,
244281
writer.setValue(realm.getOauthScopes());
245282
writer.endNode();
246283

284+
writer.startNode("emailDomains");
285+
writer.setValue(realm.getEmailDomains());
286+
writer.endNode();
287+
288+
writer.startNode("forceGithubEmail");
289+
//TODO: Is there a better way to do this?
290+
if (realm.getForceGithubEmail()) {
291+
writer.setValue("true");
292+
}
293+
else {
294+
writer.setValue("false");
295+
}
296+
writer.endNode();
297+
247298
}
248299

249300
public Object unmarshal(HierarchicalStreamReader reader,
@@ -270,6 +321,10 @@ public Object unmarshal(HierarchicalStreamReader reader,
270321
realm.setGithubApiUri(DEFAULT_API_URI);
271322
}
272323

324+
if (realm.getForceGithubEmail() == null) {
325+
realm.setForceGithubEmail(DEFAULT_FORCE_GITHUB_EMAIL);
326+
}
327+
273328
return realm;
274329
}
275330

@@ -289,6 +344,15 @@ private void setValue(GithubSecurityRealm realm, String node,
289344
realm.setGithubApiUri(value);
290345
} else if (node.toLowerCase().equals("oauthscopes")) {
291346
realm.setOauthScopes(value);
347+
} else if (node.toLowerCase().equals("emaildomains")) {
348+
realm.setEmailDomains(value);
349+
} else if (node.toLowerCase().equals("forcegithubemail")) {
350+
if (value.toLowerCase().equals("true")){
351+
realm.setForceGithubEmail(true);
352+
}
353+
else {
354+
realm.setForceGithubEmail(false);
355+
}
292356
} else {
293357
throw new ConversionException("Invalid node value = " + node);
294358
}
@@ -333,6 +397,20 @@ public String getOauthScopes() {
333397
return oauthScopes;
334398
}
335399

400+
/**
401+
* @return the emailDomains
402+
*/
403+
public String getEmailDomains() {
404+
return emailDomains;
405+
}
406+
407+
/**
408+
* @return the forceGithubEmail
409+
*/
410+
public Boolean getForceGithubEmail() {
411+
return forceGithubEmail;
412+
}
413+
336414
public HttpResponse doCommenceLogin(StaplerRequest request, @Header("Referer") final String referer)
337415
throws IOException {
338416
request.getSession().setAttribute(REFERER_ATTRIBUTE,referer);
@@ -383,17 +461,40 @@ public HttpResponse doFinishLogin(StaplerRequest request)
383461
GithubSecretStorage.put(u, accessToken);
384462

385463
u.setFullName(self.getName());
386-
// Set email from github only if empty
387-
if (!u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) {
464+
// Set email from github only if empty or forceGithubEmail flag is set
465+
if (forceGithubEmail || !u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) {
388466
if(hasScope("user") || hasScope("user:email")) {
389467
String primary_email = null;
390-
for(GHEmail e : self.getEmails2()) {
391-
if(e.isPrimary()) {
392-
primary_email = e.getEmail();
468+
String domain_email = null;
469+
if (emailDomains != null) {
470+
LOGGER.log(Level.FINE, "Searching for email of github user \"" + u.getId() + "\" that match domain(s) \"" + emailDomains + "\"");
471+
for (String emailDomain : emailDomains.split(",")) {
472+
for(GHEmail e : self.getEmails2()) {
473+
LOGGER.log(Level.FINE, "Checking if email \"" + e.getEmail() + "\" matches domain \"" + emailDomain + "\" for github user \"" + u.getId() + "\"");
474+
if(e.getEmail().endsWith("@" + emailDomain)) {
475+
domain_email = e.getEmail();
476+
LOGGER.log(Level.FINE, "Email \"" + e.getEmail() + "\" matches domain \"" + emailDomain + "\" for github user \"" + u.getId() + "\"");
477+
break;
478+
}
479+
}
480+
if (domain_email != null) {
481+
LOGGER.log(Level.FINE, "Setting email for github user \"" + u.getId() + "\" to \"" + domain_email + "\" due to matching domain in domain list");
482+
u.addProperty(new Mailer.UserProperty(domain_email));
483+
break;
484+
}
393485
}
394486
}
395-
if(primary_email != null) {
396-
u.addProperty(new Mailer.UserProperty(primary_email));
487+
if (domain_email == null) {
488+
LOGGER.log(Level.FINE, "Getting primary email for github user \"" + u.getId() + "\"");
489+
for(GHEmail e : self.getEmails2()) {
490+
LOGGER.log(Level.FINE, "Checking if email \"" + e.getEmail() + "\" is primary email for github user \"" + u.getId() + "\"");
491+
if (e.isPrimary()) {
492+
primary_email = e.getEmail();
493+
LOGGER.log(Level.FINE, "Setting email for github user \"" + u.getId() + "\" to primary address \"" + primary_email + "\"");
494+
u.addProperty(new Mailer.UserProperty(primary_email));
495+
break;
496+
}
497+
}
397498
}
398499
} else {
399500
u.addProperty(new Mailer.UserProperty(auth.getGitHub().getMyself().getEmail()));
@@ -600,6 +701,10 @@ public String getDefaultOauthScopes() {
600701
return DEFAULT_OAUTH_SCOPES;
601702
}
602703

704+
public Boolean getDefaultForceGithubEmail() {
705+
return DEFAULT_FORCE_GITHUB_EMAIL;
706+
}
707+
603708
public DescriptorImpl() {
604709
super();
605710
// TODO Auto-generated constructor stub
@@ -700,7 +805,9 @@ public boolean equals(Object object){
700805
this.getGithubApiUri().equals(obj.getGithubApiUri()) &&
701806
this.getClientID().equals(obj.getClientID()) &&
702807
this.getClientSecret().equals(obj.getClientSecret()) &&
703-
this.getOauthScopes().equals(obj.getOauthScopes());
808+
this.getOauthScopes().equals(obj.getOauthScopes()) &&
809+
this.getEmailDomains().equals(obj.getEmailDomains()) &&
810+
this.getForceGithubEmail().equals(obj.getForceGithubEmail());
704811
} else {
705812
return false;
706813
}

src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,13 @@
2323
<f:entry title="OAuth Scope(s)" field="oauthScopes" help="/plugin/github-oauth/help/realm/oauth-scopes-help.html">
2424
<f:textbox default="${descriptor.getDefaultOauthScopes()}" />
2525
</f:entry>
26+
<f:advanced>
27+
<f:entry title="Email Domains" field="emailDomains" help="/plugin/github-oauth/help/realm/email-domain.html">
28+
<f:textbox />
29+
</f:entry>
30+
<f:entry title="Force Email from github for existing users" field="forceGithubEmail" help="/plugin/github-oauth/help/realm/force-github-email.html">
31+
<f:checkbox />
32+
</f:entry>
33+
</f:advanced>
2634
</f:section>
2735
</j:jelly>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
Specify the domain(s) to prefer emails from in a comma seperated list in the prefered order. Useful if users in your users have personal and organization emails on Github and you want to ensure that their email in Jenkins is the one with your Org's domain. Examples: "example.com", "example.com,example.net" (If user has both example.com and example.net, example.com will be chosen)
3+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
If selected the email found in Github will overwrite the email stored in Jenkins, every time the user logs in via Github Oauth.
3+
</div>

src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertyTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,17 @@ private void setupRealm(){
266266
String clientID = "xxx";
267267
String clientSecret = "yyy";
268268
String oauthScopes = "read:org";
269+
String emailDomain = "";
270+
Boolean forceGithubEmail = false;
269271

270272
GithubSecurityRealm githubSecurityRealm = new GithubSecurityRealm(
271273
githubWebUri,
272274
githubApiUri,
273275
clientID,
274276
clientSecret,
275-
oauthScopes
277+
oauthScopes,
278+
emailDomain,
279+
forceGithubEmail
276280
);
277281

278282
j.jenkins.setSecurityRealm(githubSecurityRealm);

src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,29 +42,50 @@ public class GithubSecurityRealmTest {
4242
public void testEquals_true() {
4343
GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org");
4444
GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org");
45+
GithubSecurityRealm c = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "", false);
46+
GithubSecurityRealm d = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "example.com", false);
47+
GithubSecurityRealm e = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "example.com", false);
4548
assertTrue(a.equals(b));
49+
assertTrue(b.equals(c));
50+
assertTrue(c.equals(b));
51+
assertTrue(d.equals(e));
52+
assertTrue(e.equals(d));
4653
}
4754

4855
@Test
4956
public void testEquals_false() {
5057
GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org");
5158
GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo");
59+
GithubSecurityRealm c = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "", false);
60+
GithubSecurityRealm d = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo", "", false);
61+
GithubSecurityRealm e = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo", "example.com", false);
5262
assertFalse(a.equals(b));
63+
assertFalse(a.equals(d));
5364
assertFalse(a.equals(""));
65+
assertFalse(c.equals(b));
66+
assertFalse(c.equals(d));
67+
assertFalse(c.equals(""));
68+
assertFalse(d.equals(e));
5469
}
5570

5671
@Test
5772
public void testHasScope_true() {
5873
GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email");
74+
GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email", "example.com", false);
5975
assertTrue(a.hasScope("user"));
6076
assertTrue(a.hasScope("read:org"));
6177
assertTrue(a.hasScope("user:email"));
78+
assertTrue(b.hasScope("user"));
79+
assertTrue(b.hasScope("read:org"));
80+
assertTrue(b.hasScope("user:email"));
6281
}
6382

6483
@Test
6584
public void testHasScope_false() {
6685
GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email");
86+
GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email", "example.com", false);
6787
assertFalse(a.hasScope("somescope"));
88+
assertFalse(b.hasScope("somescope"));
6889
}
6990

7091
@Test

0 commit comments

Comments
 (0)