Skip to content

Commit ad24059

Browse files
Add email and display name header injection support - revised from PR #30 (#124)
* Initial commit * Apply suggestions from code review * Added tests * Fixed tests * Pre commit changes * fix: reformatting using mvn spotless:apply --------- Co-authored-by: Steve Boardwell <steve.boardwell@gmail.com>
1 parent d5ad0c4 commit ad24059

File tree

6 files changed

+179
-2
lines changed

6 files changed

+179
-2
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ When it comes to authorisation, the offers two options to developers: HTTP heade
77
## The default values for the HTTP header fields are:
88

99
1. Header User Name: X-Forwarded-User
10-
2. Header Groups Name: X-Forwarded-Groups
11-
3. Header Groups Delimiter: |
10+
2. Header User Mail: X-Forwarded-Mail
11+
3. Header User Display Name: X-Forwarded-DisplayName
12+
4. Header Groups Name: X-Forwarded-Groups
13+
5. Header Groups Delimiter: |
1214

1315
The LDAP options can be displayed via the Advanced... button, located on the right side of the security settings.
1416

@@ -73,6 +75,8 @@ The default values for the HTTP header fields are:
7375
# Remove these header before to set the right value after, it prevent the client from setting this header
7476
RequestHeader unset "X-Forwarded-User"
7577
RequestHeader unset "X-Forwarded-Groups"
78+
RequestHeader unset "X-Forwarded-Mail"
79+
RequestHeader unset "X-Forwarded-DisplayName"
7680
# Remove the basic authorization header to avoid to use it in Jenkins
7781
RequestHeader unset "Authorization"
7882

@@ -84,6 +88,9 @@ The default values for the HTTP header fields are:
8488
RequestHeader set "X-Forwarded-User" "%{RU}e"
8589
# Groups are separated by |
8690
RequestHeader set "X-Forwarded-Groups" "%{RU}e|users"
91+
# Inject mail & display name
92+
RequestHeader set "X-Forwarded-Mail" %{AUTHENTICATE_MAIL}e
93+
RequestHeader set "X-Forwarded-DisplayName" %{AUTHENTICATE_DISPLAYNAME}e
8794

8895
# strip the REALM of Kerberos Login
8996
# RequestHeader edit X-Forwarded-User "@REALM$" ""

src/main/java/org/jenkinsci/plugins/reverse_proxy_auth/ReverseProxySecurityRealm.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import org.jenkinsci.plugins.reverse_proxy_auth.auth.ReverseProxyAuthenticationProvider;
9595
import org.jenkinsci.plugins.reverse_proxy_auth.auth.ReverseProxyAuthoritiesPopulator;
9696
import org.jenkinsci.plugins.reverse_proxy_auth.auth.ReverseProxyAuthoritiesPopulatorImpl;
97+
import org.jenkinsci.plugins.reverse_proxy_auth.data.ForwardedUserData;
9798
import org.jenkinsci.plugins.reverse_proxy_auth.data.GroupSearchTemplate;
9899
import org.jenkinsci.plugins.reverse_proxy_auth.data.SearchTemplate;
99100
import org.jenkinsci.plugins.reverse_proxy_auth.data.UserSearchTemplate;
@@ -139,6 +140,16 @@ public class ReverseProxySecurityRealm extends SecurityRealm {
139140
/** Search Template used when the groups are in the header. */
140141
private ReverseProxySearchTemplate proxyTemplate;
141142

143+
/**
144+
* The name of the header which the email has to be extracted from.
145+
*/
146+
public final String forwardedEmail;
147+
148+
/**
149+
* The name of the header which the display name has to be extracted from.
150+
*/
151+
public final String forwardedDisplayName;
152+
142153
/** Created in {@link #createSecurityComponents()}. Can be used to connect to LDAP. */
143154
private transient LdapTemplate ldapTemplate;
144155

@@ -262,6 +273,8 @@ public class ReverseProxySecurityRealm extends SecurityRealm {
262273
@DataBoundConstructor
263274
public ReverseProxySecurityRealm(
264275
String forwardedUser,
276+
String forwardedEmail,
277+
String forwardedDisplayName,
265278
String headerGroups,
266279
String headerGroupsDelimiter,
267280
String customLogInUrl,
@@ -283,6 +296,8 @@ public ReverseProxySecurityRealm(
283296
String emailAddressLdapAttribute) {
284297

285298
this.forwardedUser = fixEmptyAndTrim(forwardedUser);
299+
this.forwardedEmail = fixEmptyAndTrim(forwardedEmail);
300+
this.forwardedDisplayName = fixEmptyAndTrim(forwardedDisplayName);
286301

287302
this.headerGroups = headerGroups;
288303
if (!StringUtils.isBlank(headerGroupsDelimiter)) {
@@ -529,6 +544,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
529544
}
530545

531546
} else {
547+
// Without LDAP, retrieve user data from the headers
548+
ForwardedUserData forwardedData = retrieveForwardedData(r);
549+
User user = User.get(userFromHeader);
550+
if (user != null) {
551+
forwardedData.update(user);
552+
}
532553
String groups = r.getHeader(headerGroups);
533554

534555
List<GrantedAuthority> localAuthorities = new ArrayList<GrantedAuthority>();
@@ -576,6 +597,17 @@ public void destroy() {}
576597
return new ChainedServletFilter(defaultFilter, filter);
577598
}
578599

600+
private ForwardedUserData retrieveForwardedData(HttpServletRequest r) {
601+
ForwardedUserData toReturn = new ForwardedUserData();
602+
if (forwardedEmail != null) {
603+
toReturn.setEmail(r.getHeader(forwardedEmail));
604+
}
605+
if (forwardedDisplayName != null) {
606+
toReturn.setDisplayName(r.getHeader(forwardedDisplayName));
607+
}
608+
return toReturn;
609+
}
610+
579611
@Override
580612
public boolean canLogOut() {
581613
if (customLogOutUrl == null) {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.jenkinsci.plugins.reverse_proxy_auth.data;
2+
3+
import hudson.model.User;
4+
import hudson.tasks.Mailer;
5+
import java.io.IOException;
6+
7+
/**
8+
* User data forwarded by the reverse proxy
9+
* **/
10+
public class ForwardedUserData {
11+
/** Empty header may be a null string **/
12+
private static final String NULL_HEADER = "(null)";
13+
14+
private String email;
15+
private String displayName;
16+
17+
public String getEmail() {
18+
return email;
19+
}
20+
21+
public void setEmail(String email) {
22+
this.email = email;
23+
}
24+
25+
public String getDisplayName() {
26+
return displayName;
27+
}
28+
29+
public void setDisplayName(String displayName) {
30+
this.displayName = displayName;
31+
}
32+
33+
/**
34+
* Update the forwarded data to the jenkins user.
35+
* @return true if updated and saved
36+
* **/
37+
public boolean update(User user) {
38+
boolean toReturn = false;
39+
if (updateDisplayName(user) || updateEmail(user)) {
40+
toReturn = true;
41+
try {
42+
user.save();
43+
} catch (IOException e) {
44+
throw new IllegalStateException(e);
45+
}
46+
}
47+
48+
return toReturn;
49+
}
50+
51+
private boolean updateDisplayName(User user) {
52+
boolean toReturn = false;
53+
if (isNotNullHeader(displayName) && !displayName.equals(user.getFullName())) {
54+
user.setFullName(displayName);
55+
toReturn = true;
56+
}
57+
return toReturn;
58+
}
59+
60+
private boolean updateEmail(User user) {
61+
boolean toReturn = false;
62+
if (isNotNullHeader(email)) {
63+
Mailer.UserProperty emailProp = user.getProperty(Mailer.UserProperty.class);
64+
if (emailProp == null || !email.equals(emailProp.getConfiguredAddress())) {
65+
emailProp = new Mailer.UserProperty(email);
66+
try {
67+
user.addProperty(emailProp);
68+
} catch (IOException e) {
69+
throw new IllegalStateException(e);
70+
}
71+
toReturn = true;
72+
}
73+
}
74+
return toReturn;
75+
}
76+
77+
private static boolean isNotNullHeader(String value) {
78+
return value != null && !value.equals(NULL_HEADER);
79+
}
80+
}

src/main/resources/org/jenkinsci/plugins/reverse_proxy_auth/ReverseProxySecurityRealm/config.jelly

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ THE SOFTWARE.
2626
<f:entry title="${%Header User Name}">
2727
<f:textbox name="forwardedUser" value="${instance.forwardedUser}" default="X-Forwarded-User" />
2828
</f:entry>
29+
<f:entry title="${%Header User Mail}">
30+
<f:textbox name="forwardedEmail" value="${instance.forwardedEmail}" default="X-Forwarded-Mail" />
31+
</f:entry>
32+
<f:entry title="${%Header User Display Name}">
33+
<f:textbox name="forwardedDisplayName" value="${instance.forwardedDisplayName}" default="X-Forwarded-DisplayName" />
34+
</f:entry>
2935
<f:entry title="${%Header Groups Name}">
3036
<f:textbox field="headerGroups" default="X-Forwarded-Groups" />
3137
</f:entry>

src/test/java/org/jenkinsci/plugins/reverse_proxy_auth/ReverseProxySecurityRealmTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ private ReverseProxySecurityRealm createBasicRealm() {
6969
return new ReverseProxySecurityRealm(
7070
"X-Forwarded-User", // forwardedUser
7171
"X-Forwarded-Groups", // headerGroups
72+
"X-Forwarded-Email", // forwardedEmail
73+
"X-Forwarded-DisplayName", // forwardedDisplayName
7274
"|", // headerGroupsDelimiter
7375
"", // customLogInUrl
7476
"", // customLogOutUrl
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.jenkinsci.plugins.reverse_proxy_auth.data;
2+
3+
import hudson.model.User;
4+
import hudson.tasks.Mailer;
5+
import jenkins.model.Jenkins;
6+
import org.junit.Assert;
7+
import org.junit.Before;
8+
import org.junit.Rule;
9+
import org.junit.Test;
10+
import org.jvnet.hudson.test.JenkinsRule;
11+
import org.jvnet.hudson.test.MockAuthorizationStrategy;
12+
13+
public class ForwardedUserDataTest {
14+
private ForwardedUserData forwardedUserData;
15+
private User user;
16+
17+
@Rule
18+
public JenkinsRule j = new JenkinsRule();
19+
20+
@Before
21+
public void setup() {
22+
j.jenkins.setAuthorizationStrategy(
23+
new MockAuthorizationStrategy().grant(Jenkins.READ).everywhere().to("Max Mustermann"));
24+
25+
forwardedUserData = new ForwardedUserData();
26+
user = User.getOrCreateByIdOrFullName("Max Mustermann");
27+
}
28+
29+
@Test
30+
public void basicForwardedUserData() {
31+
forwardedUserData.setEmail("max.mustermann@example.com");
32+
Assert.assertEquals("max.mustermann@example.com", forwardedUserData.getEmail());
33+
34+
forwardedUserData.setDisplayName("Max Mustermann");
35+
Assert.assertEquals("Max Mustermann", forwardedUserData.getDisplayName());
36+
}
37+
38+
@Test
39+
public void testUpdate() {
40+
user.setFullName("John Doe");
41+
forwardedUserData.setDisplayName("Max Mustermann");
42+
forwardedUserData.update(user);
43+
Assert.assertEquals("Max Mustermann", user.getFullName());
44+
45+
forwardedUserData.setEmail("max.mustermann@example.com");
46+
forwardedUserData.update(user);
47+
Mailer.UserProperty emailProp = user.getProperty(Mailer.UserProperty.class);
48+
Assert.assertEquals("max.mustermann@example.com", emailProp.getAddress());
49+
}
50+
}

0 commit comments

Comments
 (0)