Skip to content

Commit 4d7765c

Browse files
rsandelljgreffe
authored andcommitted
SECURITY-3461
1 parent 2849bd3 commit 4d7765c

File tree

15 files changed

+341
-7
lines changed

15 files changed

+341
-7
lines changed

src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import edu.umd.cs.findbugs.annotations.NonNull;
4242
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4343
import hudson.Extension;
44+
import hudson.ExtensionList;
4445
import hudson.Util;
4546
import hudson.model.Descriptor;
4647
import hudson.model.Descriptor.FormException;
@@ -88,6 +89,8 @@
8889
import java.util.logging.Logger;
8990
import java.util.regex.Pattern;
9091
import javax.annotation.PostConstruct;
92+
import jenkins.model.IdStrategy;
93+
import jenkins.model.IdStrategyDescriptor;
9194
import jenkins.model.Jenkins;
9295
import jenkins.security.ApiTokenProperty;
9396
import jenkins.security.FIPS140;
@@ -151,6 +154,8 @@ public class OicSecurityRealm extends SecurityRealm implements Serializable {
151154
private static final long serialVersionUID = 1L;
152155

153156
private static final Logger LOGGER = Logger.getLogger(OicSecurityRealm.class.getName());
157+
private IdStrategy userIdStrategy;
158+
private IdStrategy groupIdStrategy;
154159

155160
public static enum TokenAuthMethod {
156161
client_secret_basic(ClientAuthenticationMethod.CLIENT_SECRET_BASIC),
@@ -316,7 +321,9 @@ public OicSecurityRealm(
316321
String clientId,
317322
Secret clientSecret,
318323
OicServerConfiguration serverConfiguration,
319-
Boolean disableSslVerification)
324+
Boolean disableSslVerification,
325+
IdStrategy userIdStrategy,
326+
IdStrategy groupIdStrategy)
320327
throws IOException, FormException {
321328
// Needed in DataBoundSetter
322329
this.disableSslVerification = Util.fixNull(disableSslVerification, Boolean.FALSE);
@@ -327,6 +334,8 @@ public OicSecurityRealm(
327334
this.clientId = clientId;
328335
this.clientSecret = clientSecret;
329336
this.serverConfiguration = serverConfiguration;
337+
this.userIdStrategy = userIdStrategy;
338+
this.groupIdStrategy = groupIdStrategy;
330339
}
331340

332341
@SuppressWarnings("deprecated")
@@ -420,6 +429,20 @@ public String getUserNameField() {
420429
return userNameField;
421430
}
422431

432+
@Restricted(NoExternalUse.class)
433+
public boolean isMissingIdStrategy() {
434+
return userIdStrategy == null || groupIdStrategy == null;
435+
}
436+
437+
@Override
438+
public IdStrategy getUserIdStrategy() {
439+
if (userIdStrategy != null) {
440+
return userIdStrategy;
441+
} else {
442+
return IdStrategy.CASE_INSENSITIVE;
443+
}
444+
}
445+
423446
public String getTokenFieldToCheckKey() {
424447
return tokenFieldToCheckKey;
425448
}
@@ -440,6 +463,15 @@ public String getGroupsFieldName() {
440463
return groupsFieldName;
441464
}
442465

466+
@Override
467+
public IdStrategy getGroupIdStrategy() {
468+
if (groupIdStrategy != null) {
469+
return groupIdStrategy;
470+
} else {
471+
return IdStrategy.CASE_INSENSITIVE;
472+
}
473+
}
474+
443475
public boolean isDisableSslVerification() {
444476
return disableSslVerification;
445477
}
@@ -1628,5 +1660,26 @@ public Descriptor<OicServerConfiguration> getDefaultServerConfigurationType() {
16281660
public boolean isFipsEnabled() {
16291661
return FIPS140.useCompliantAlgorithms();
16301662
}
1663+
1664+
@Restricted(NoExternalUse.class)
1665+
public List<IdStrategyDescriptor> getIdStrategyDescriptors() {
1666+
return ExtensionList.lookup(IdStrategyDescriptor.class);
1667+
}
1668+
1669+
/**
1670+
* The default username strategy for new OicSecurityRealm
1671+
*/
1672+
@Restricted(NoExternalUse.class)
1673+
public IdStrategy defaultUsernameIdStrategy() {
1674+
return new IdStrategy.CaseSensitive();
1675+
}
1676+
1677+
/**
1678+
* The default group strategy for new OicSecurityRealm
1679+
*/
1680+
@Restricted(NoExternalUse.class)
1681+
public IdStrategy defaultGroupIdStrategy() {
1682+
return new IdStrategy.CaseSensitive();
1683+
}
16311684
}
16321685
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.jenkinsci.plugins.oic.monitor;
2+
3+
import com.google.common.annotations.VisibleForTesting;
4+
import hudson.Extension;
5+
import hudson.ExtensionList;
6+
import hudson.model.AdministrativeMonitor;
7+
import hudson.security.SecurityRealm;
8+
import java.io.IOException;
9+
import jenkins.model.Jenkins;
10+
import org.jenkinsci.plugins.oic.Messages;
11+
import org.jenkinsci.plugins.oic.OicSecurityRealm;
12+
import org.kohsuke.accmod.Restricted;
13+
import org.kohsuke.accmod.restrictions.NoExternalUse;
14+
import org.kohsuke.stapler.HttpResponse;
15+
import org.kohsuke.stapler.HttpResponses;
16+
import org.kohsuke.stapler.interceptor.RequirePOST;
17+
18+
@Extension
19+
@Restricted(NoExternalUse.class)
20+
public class OicIdStrategyMonitor extends AdministrativeMonitor {
21+
22+
// if null, means not evaluated yet
23+
Boolean missingIdStrategy;
24+
25+
public OicIdStrategyMonitor() {}
26+
27+
@VisibleForTesting
28+
protected static OicIdStrategyMonitor get() {
29+
return ExtensionList.lookupSingleton(OicIdStrategyMonitor.class);
30+
}
31+
32+
@Override
33+
public String getDisplayName() {
34+
return Messages.OicSecurityRealm_monitor_DisplayName();
35+
}
36+
37+
@Override
38+
public boolean isActivated() {
39+
if (!Boolean.FALSE.equals(missingIdStrategy)) {
40+
SecurityRealm securityRealm = Jenkins.get().getSecurityRealm();
41+
if (securityRealm instanceof OicSecurityRealm) {
42+
missingIdStrategy = ((OicSecurityRealm) securityRealm).isMissingIdStrategy();
43+
} else {
44+
missingIdStrategy = Boolean.FALSE;
45+
}
46+
}
47+
return missingIdStrategy;
48+
}
49+
50+
@RequirePOST
51+
public HttpResponse doForward() throws IOException {
52+
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
53+
return HttpResponses.redirectViaContextPath("configureSecurity");
54+
}
55+
}

src/main/resources/org/jenkinsci/plugins/oic/Messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ OicSecurityRealm.DisableSslVerificationFipsMode = SSL verification can not be di
2727
OicSecurityRealm.DisableTokenVerificationFipsMode = Token verification can not be disabled in FIPS mode
2828
OicServerWellKnownConfiguration.DisplayName = Discovery via well-known endpoint
2929
OicServerManualConfiguration.DisplayName = Manual entry
30+
OicSecurityRealm.monitor.DisplayName= Openid Connect Id Strategy Configuration

src/main/resources/org/jenkinsci/plugins/oic/OicSecurityRealm/config.jelly

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
<f:entry title="${%UsernameFieldName}" field="userNameField">
1717
<f:textbox/>
1818
</f:entry>
19+
<f:entry>
20+
<f:dropdownDescriptorSelector title="${%UsernameIdStrategy}" field="userIdStrategy" default="${descriptor.defaultUsernameIdStrategy()}" descriptors="${descriptor.idStrategyDescriptors}"/>
21+
</f:entry>
1922
<f:entry title="${%FullnameFieldName}" field="fullNameFieldName">
2023
<f:textbox/>
2124
</f:entry>
@@ -25,6 +28,9 @@
2528
<f:entry title="${%GroupsFieldName}" field="groupsFieldName">
2629
<f:textbox/>
2730
</f:entry>
31+
<f:entry>
32+
<f:dropdownDescriptorSelector title="${%GroupIdStrategy}" field="groupIdStrategy" default="${descriptor.defaultGroupIdStrategy()}" descriptors="${descriptor.idStrategyDescriptors}"/>
33+
</f:entry>
2834
</f:advanced>
2935
<f:entry title="${%LogoutFromOpenIDProvider}" field="logoutFromOpenidProvider">
3036
<f:checkbox id="logoutFromIDP"/>

src/main/resources/org/jenkinsci/plugins/oic/OicSecurityRealm/config.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ UserFields=User fields
2626
Username=Username
2727
UsernameFieldName=User name field name
2828
WellknownConfigurationEndpoint=Well-known configuration endpoint
29+
UsernameIdStrategy=Username case sensitivity
30+
GroupIdStrategy=Group name case sensitivity
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:f="/lib/form" xmlns:i="jelly:fmt" xmlns:st="jelly:stapler">
3+
${%blurb}
4+
</j:jelly>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blurb=The OpenId Connect Security Realm's "Username case sensitivity" and "Group name case sensitivity" options have not been configured.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:l="/lib/layout">
3+
<div class="alert alert-warning">
4+
<l:isAdmin>
5+
<form method="post" action="${rootURL}/${it.url}/forward">
6+
<f:submit value="${%configureSecurityRealm}"/>
7+
</form>
8+
</l:isAdmin>
9+
<j:set var="actionAnchor">
10+
<a href="${rootURL}/configure">${%actionUrlContent}</a>
11+
</j:set>
12+
${%blurb}
13+
</div>
14+
</j:jelly>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
blurb=\
2+
<p>The Openid Connect Security Realm was configured before the introduction of the "Username case sensitivity" and "Group name case sensitivity" options.</p> \
3+
<p>As a result, the current configuration treats these values as case-insensitive, whereas the default for new configurations is case-sensitive.</p> \
4+
<p>This difference could introduce a security vulnerability depending on your specific use case. For further information, refer to the <a href="https://www.jenkins.io/security/advisory/2025-01-22/#SECURITY-3461">security advisory</a>. Please review and select the appropriate case sensitivity settings for your environment, then save the updated security realm configuration.</p> \
5+
<p>Warning: Switching from case-insensitive to case-sensitive behavior can be a lossy operation if there are mixed-case usernames or group names. Users with externally-defined mixed-case names will effectively be treated as new users they next time they log in, and will lose their existing user preferences. Group-related configurations in Jenkins for externally-defined groups with mixed case names will no longer apply to members of those external groups. Users with mixed-case names previously defined in groups in Jenkins will also no longer be considered members of those groups after the switch.</p>
6+
configureSecurityRealm=Configure the Security Realm

src/test/java/org/jenkinsci/plugins/oic/OicSecurityRealmFipsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ public class OicSecurityRealmFipsTest {
3737
@Test
3838
@WithoutJenkins
3939
public void settingNonCompliantValuesNotAllowedTest() throws IOException, Descriptor.FormException {
40-
OicSecurityRealm realm = new OicSecurityRealm("clientId", Secret.fromString("secret"), null, false);
40+
OicSecurityRealm realm = new OicSecurityRealm("clientId", Secret.fromString("secret"), null, false, null, null);
4141
Descriptor.FormException ex = assertThrows(
4242
Descriptor.FormException.class,
43-
() -> new OicSecurityRealm("clientId", Secret.fromString("secret"), null, true));
43+
() -> new OicSecurityRealm("clientId", Secret.fromString("secret"), null, true, null, null));
4444
assertThat(
4545
"Exception contains the reason",
4646
ex.getMessage(),

0 commit comments

Comments
 (0)