8000 WIP: Add email domain filter by jdmulloy · Pull Request #98 · jenkinsci/github-oauth-plugin · GitHub
[go: up one dir, main page]

Skip to content

WIP: Add email domain filter #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 116 additions & 9 deletions src/main/java/org/jenkinsci/plugins/GithubSecurityRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

/**
Expand All @@ -123,20 +127,39 @@ 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) {
Copy link
Member
@samrocketman samrocketman Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not cleanly migrate configuration. Please overload this method with another deprecated method so that configurations cleanly migrate.

super();

this.githubWebUri = Util.fixEmptyAndTrim(githubWebUri);
this.githubApiUri = Util.fixEmptyAndTrim(githubApiUri);
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() { }
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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?
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better/preferred way to store a boolean value in the config file? If not and what I have is ok I'll remove the TODO comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check how other plugins save and load booleans. I'll check as well.

if (realm.getForceGithubEmail()) {
writer.setValue("true");
}
else {
writer.setValue("false");
}
writer.endNode();

}

public Object unmarshal(HierarchicalStreamReader reader,
Expand All @@ -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;
}

Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,13 @@
<f:entry title="OAuth Scope(s)" field="oauthScopes" help="/plugin/github-oauth/help/realm/oauth-scopes-help.html">
<f:textbox default="${descriptor.getDefaultOauthScopes()}" />
</f:entry>
<f:advanced>
<f:entry title="Email Domains" field="emailDomains" help="/plugin/github-oauth/help/realm/email-domain.html">
<f:textbox />
</f:entry>
<f:entry title="Force Email from github for existing users" field="forceGithubEmail" help="/plugin/github-oauth/help/realm/force-github-email.html">
<f:checkbox />
</f:entry>
</f:advanced>
</f:section>
</j:jelly>
3 changes: 3 additions & 0 deletions src/main/webapp/help/realm/email-domain.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
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)
</div>
3 changes: 3 additions & 0 deletions src/main/webapp/help/realm/force-github-email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
If selected the email found in Github will overwrite the email stored in Jenkins, every time the user logs in via Github Oauth.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
0