diff --git a/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java b/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java index 14416c03..b8762197 100644 --- a/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java +++ b/src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java @@ -91,6 +91,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; +import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -107,12 +108,15 @@ public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm impl private static final String DEFAULT_API_URI = "https://api.github.com"; private static final String DEFAULT_ENTERPRISE_API_SUFFIX = "/api/v3"; private static final String DEFAULT_OAUTH_SCOPES = "read:org,user:email,repo"; + private static final Boolean DEFAULT_FORCE_GITHUB_EMAIL = false; private String githubWebUri; private String githubApiUri; private String clientID; private Secret clientSecret; private String oauthScopes; + private String emailDomains; + private Boolean forceGithubEmail; private String[] myScopes; /** @@ -123,13 +127,17 @@ public class GithubSecurityRealm extends AbstractPasswordBasedSecurityRealm impl * @param clientID The client ID for the created OAuth Application. * @param clientSecret The client secret for the created GitHub OAuth Application. * @param oauthScopes A comma separated list of OAuth Scopes to request access to. + * @param emailDomains An optional comma separated list of domain(s) to select for email + * @param forceGithubEmail Force the email from github to override the one in the profile */ @DataBoundConstructor public GithubSecurityRealm(String githubWebUri, String githubApiUri, String clientID, String clientSecret, - String oauthScopes) { + String oauthScopes, + String emailDomains, + Boolean forceGithubEmail) { super(); this.githubWebUri = Util.fixEmptyAndTrim(githubWebUri); @@ -137,6 +145,21 @@ public GithubSecurityRealm(String githubWebUri, this.clientID = Util.fixEmptyAndTrim(clientID); setClientSecret(Util.fixEmptyAndTrim(clientSecret)); this.oauthScopes = Util.fixEmptyAndTrim(oauthScopes); + this.emailDomains = emailDomains.trim(); + this.forceGithubEmail = forceGithubEmail; + } + + /** + This method is deprecated. + @deprecated use GithubSecurityRealm(githubWebUri, githubApiUri, clientID, clientSecret, oauthScopes, emailDomains, forceGithubEmail) + */ + @Deprecated + public GithubSecurityRealm(String githubWebUri, + String githubApiUri, + String clientID, + String clientSecret, + String oauthScopes) { + this(githubWebUri, githubApiUri, clientID, clientSecret, oauthScopes, "", false); } private GithubSecurityRealm() { } @@ -186,6 +209,20 @@ private void setOauthScopes(String oauthScopes) { this.oauthScopes = oauthScopes; } + /** + * @param emailDomains the emailDomains to set + */ + private void setEmailDomains(String emailDomains) { + this.emailDomains = emailDomains; + } + + /** + * @param forceGithubEmail the forceGithubEmail to set + */ + private void setForceGithubEmail(Boolean forceGithubEmail) { + this.forceGithubEmail = forceGithubEmail; + } + /** * Checks the security realm for a GitHub OAuth scope. * @param scope A scope to check for in the security realm. @@ -244,6 +281,20 @@ public void marshal(Object source, HierarchicalStreamWriter writer, writer.setValue(realm.getOauthScopes()); writer.endNode(); + writer.startNode("emailDomains"); + writer.setValue(realm.getEmailDomains()); + writer.endNode(); + + writer.startNode("forceGithubEmail"); + //TODO: Is there a better way to do this? + if (realm.getForceGithubEmail()) { + writer.setValue("true"); + } + else { + writer.setValue("false"); + } + writer.endNode(); + } public Object unmarshal(HierarchicalStreamReader reader, @@ -270,6 +321,10 @@ public Object unmarshal(HierarchicalStreamReader reader, realm.setGithubApiUri(DEFAULT_API_URI); } + if (realm.getForceGithubEmail() == null) { + realm.setForceGithubEmail(DEFAULT_FORCE_GITHUB_EMAIL); + } + return realm; } @@ -289,6 +344,15 @@ private void setValue(GithubSecurityRealm realm, String node, realm.setGithubApiUri(value); } else if (node.toLowerCase().equals("oauthscopes")) { realm.setOauthScopes(value); + } else if (node.toLowerCase().equals("emaildomains")) { + realm.setEmailDomains(value); + } else if (node.toLowerCase().equals("forcegithubemail")) { + if (value.toLowerCase().equals("true")){ + realm.setForceGithubEmail(true); + } + else { + realm.setForceGithubEmail(false); + } } else { throw new ConversionException("Invalid node value = " + node); } @@ -333,6 +397,20 @@ public String getOauthScopes() { return oauthScopes; } + /** + * @return the emailDomains + */ + public String getEmailDomains() { + return emailDomains; + } + + /** + * @return the forceGithubEmail + */ + public Boolean getForceGithubEmail() { + return forceGithubEmail; + } + public HttpResponse doCommenceLogin(StaplerRequest request, @Header("Referer") final String referer) throws IOException { request.getSession().setAttribute(REFERER_ATTRIBUTE,referer); @@ -383,17 +461,40 @@ public HttpResponse doFinishLogin(StaplerRequest request) GithubSecretStorage.put(u, accessToken); u.setFullName(self.getName()); - // Set email from github only if empty - if (!u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) { + // Set email from github only if empty or forceGithubEmail flag is set + if (forceGithubEmail || !u.getProperty(Mailer.UserProperty.class).hasExplicitlyConfiguredAddress()) { if(hasScope("user") || hasScope("user:email")) { String primary_email = null; - for(GHEmail e : self.getEmails2()) { - if(e.isPrimary()) { - primary_email = e.getEmail(); + String domain_email = null; + if (emailDomains != null) { + LOGGER.log(Level.FINE, "Searching for email of github user \"" + u.getId() + "\" that match domain(s) \"" + emailDomains + "\""); + for (String emailDomain : emailDomains.split(",")) { + for(GHEmail e : self.getEmails2()) { + LOGGER.log(Level.FINE, "Checking if email \"" + e.getEmail() + "\" matches domain \"" + emailDomain + "\" for github user \"" + u.getId() + "\""); + if(e.getEmail().endsWith("@" + emailDomain)) { + domain_email = e.getEmail(); + LOGGER.log(Level.FINE, "Email \"" + e.getEmail() + "\" matches domain \"" + emailDomain + "\" for github user \"" + u.getId() + "\""); + break; + } + } + if (domain_email != null) { + LOGGER.log(Level.FINE, "Setting email for github user \"" + u.getId() + "\" to \"" + domain_email + "\" due to matching domain in domain list"); + u.addProperty(new Mailer.UserProperty(domain_email)); + break; + } } } - if(primary_email != null) { - u.addProperty(new Mailer.UserProperty(primary_email)); + if (domain_email == null) { + LOGGER.log(Level.FINE, "Getting primary email for github user \"" + u.getId() + "\""); + for(GHEmail e : self.getEmails2()) { + LOGGER.log(Level.FINE, "Checking if email \"" + e.getEmail() + "\" is primary email for github user \"" + u.getId() + "\""); + if (e.isPrimary()) { + primary_email = e.getEmail(); + LOGGER.log(Level.FINE, "Setting email for github user \"" + u.getId() + "\" to primary address \"" + primary_email + "\""); + u.addProperty(new Mailer.UserProperty(primary_email)); + break; + } + } } } else { u.addProperty(new Mailer.UserProperty(auth.getGitHub().getMyself().getEmail())); @@ -600,6 +701,10 @@ public String getDefaultOauthScopes() { return DEFAULT_OAUTH_SCOPES; } + public Boolean getDefaultForceGithubEmail() { + return DEFAULT_FORCE_GITHUB_EMAIL; + } + public DescriptorImpl() { super(); // TODO Auto-generated constructor stub @@ -700,7 +805,9 @@ public boolean equals(Object object){ this.getGithubApiUri().equals(obj.getGithubApiUri()) && this.getClientID().equals(obj.getClientID()) && this.getClientSecret().equals(obj.getClientSecret()) && - this.getOauthScopes().equals(obj.getOauthScopes()); + this.getOauthScopes().equals(obj.getOauthScopes()) && + this.getEmailDomains().equals(obj.getEmailDomains()) && + this.getForceGithubEmail().equals(obj.getForceGithubEmail()); } else { return false; } diff --git a/src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly b/src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly index 0e39df05..ab1ebfc5 100644 --- a/src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/GithubSecurityRealm/config.jelly @@ -23,5 +23,13 @@ + + + + + + + + diff --git a/src/main/webapp/help/realm/email-domain.html b/src/main/webapp/help/realm/email-domain.html new file mode 100644 index 00000000..e04aed5d --- /dev/null +++ b/src/main/webapp/help/realm/email-domain.html @@ -0,0 +1,3 @@ +
+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) +
diff --git a/src/main/webapp/help/realm/force-github-email.html b/src/main/webapp/help/realm/force-github-email.html new file mode 100644 index 00000000..0c1bdbb0 --- /dev/null +++ b/src/main/webapp/help/realm/force-github-email.html @@ -0,0 +1,3 @@ +
+If selected the email found in Github will overwrite the email stored in Jenkins, every time the user logs in via Github Oauth. +
diff --git a/src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertyTest.java b/src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertyTest.java index a0c7ac23..f72375be 100644 --- a/src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertyTest.java +++ b/src/test/java/org/jenkinsci/plugins/GithubAccessTokenPropertyTest.java @@ -266,13 +266,17 @@ private void setupRealm(){ String clientID = "xxx"; String clientSecret = "yyy"; String oauthScopes = "read:org"; + String emailDomain = ""; + Boolean forceGithubEmail = false; GithubSecurityRealm githubSecurityRealm = new GithubSecurityRealm( githubWebUri, githubApiUri, clientID, clientSecret, - oauthScopes + oauthScopes, + emailDomain, + forceGithubEmail ); j.jenkins.setSecurityRealm(githubSecurityRealm); diff --git a/src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java b/src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java index 6d2390cf..cc30fd3f 100644 --- a/src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java +++ b/src/test/java/org/jenkinsci/plugins/GithubSecurityRealmTest.java @@ -42,29 +42,50 @@ public class GithubSecurityRealmTest { public void testEquals_true() { GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org"); GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org"); + GithubSecurityRealm c = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "", false); + GithubSecurityRealm d = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "example.com", false); + GithubSecurityRealm e = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "example.com", false); assertTrue(a.equals(b)); + assertTrue(b.equals(c)); + assertTrue(c.equals(b)); + assertTrue(d.equals(e)); + assertTrue(e.equals(d)); } @Test public void testEquals_false() { GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org"); GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo"); + GithubSecurityRealm c = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org", "", false); + GithubSecurityRealm d = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo", "", false); + GithubSecurityRealm e = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,repo", "example.com", false); assertFalse(a.equals(b)); + assertFalse(a.equals(d)); assertFalse(a.equals("")); + assertFalse(c.equals(b)); + assertFalse(c.equals(d)); + assertFalse(c.equals("")); + assertFalse(d.equals(e)); } @Test public void testHasScope_true() { GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email"); + GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email", "example.com", false); assertTrue(a.hasScope("user")); assertTrue(a.hasScope("read:org")); assertTrue(a.hasScope("user:email")); + assertTrue(b.hasScope("user")); + assertTrue(b.hasScope("read:org")); + assertTrue(b.hasScope("user:email")); } @Test public void testHasScope_false() { GithubSecurityRealm a = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email"); + GithubSecurityRealm b = new GithubSecurityRealm("http://jenkins.acme.com", "http://jenkins.acme.com/api/v3", "someid", "somesecret", "read:org,user,user:email", "example.com", false); assertFalse(a.hasScope("somescope")); + assertFalse(b.hasScope("somescope")); } @Test