Skip to content

Commit

Permalink
SECURITY-3441
Browse files Browse the repository at this point in the history
  • Loading branch information
jtnord authored and Kevin-CB committed Sep 25, 2024
1 parent 321ce67 commit 3afbfca
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 6 deletions.
15 changes: 9 additions & 6 deletions src/main/java/org/jenkinsci/plugins/oic/OicSecurityRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -823,12 +823,15 @@ private OicJsonWebTokenVerifier getJwksVerifier() {
if (jwtVerifier == null) {
jwtVerifier = new OicJsonWebTokenVerifier(
serverConfiguration.getJwksServerUrl(),
new OicJsonWebTokenVerifier.Builder().setHttpTransportFactory(new HttpTransportFactory() {
@Override
public HttpTransport create() {
return httpTransport;
}
}));
new OicJsonWebTokenVerifier.Builder()
.setHttpTransportFactory(new HttpTransportFactory() {
@Override
public HttpTransport create() {
return httpTransport;
}
})
.setIssuer(getServerConfiguration().getIssuer())
.setAudience(List.of(clientId)));
}
return jwtVerifier;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ public abstract class OicServerConfiguration extends AbstractDescribableImpl<Oic
@CheckForNull
public abstract String getEndSessionUrl();

@CheckForNull
public abstract String getIssuer();

public abstract boolean isUseRefreshTokens();
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class OicServerManualConfiguration extends OicServerConfiguration {
private String scopes = "openid email";
private String userInfoServerUrl;
private boolean useRefreshTokens;
private String issuer;

@DataBoundConstructor
public OicServerManualConfiguration(String tokenServerUrl, String authorizationServerUrl) throws FormException {
Expand All @@ -49,6 +50,11 @@ public void setEndSessionUrl(@Nullable String endSessionUrl) {
this.endSessionUrl = endSessionUrl;
}

@DataBoundSetter
public void setIssuer(@Nullable String issuer) {
this.issuer = Util.fixEmptyAndTrim(issuer);
}

@DataBoundSetter
public void setJwksServerUrl(@Nullable String jwksServerUrl) {
this.jwksServerUrl = jwksServerUrl;
Expand Down Expand Up @@ -80,6 +86,12 @@ public String getEndSessionUrl() {
return endSessionUrl;
}

@Override
@CheckForNull
public String getIssuer() {
return issuer;
}

@Override
public boolean isUseRefreshTokens() {
return useRefreshTokens;
Expand Down Expand Up @@ -154,6 +166,15 @@ public FormValidation doCheckEndSessionUrl(@QueryParameter String value) {
}
}

@POST
public FormValidation doCheckIssuer(@QueryParameter String issuer) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
if (Util.fixEmptyAndTrim(issuer) == null) {
return FormValidation.warning(Messages.OicSecurityRealm_IssuerRecommended());
}
return FormValidation.ok();

Check warning on line 175 in src/main/java/org/jenkinsci/plugins/oic/OicServerManualConfiguration.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 171-175 are not covered by tests
}

@POST
public FormValidation doCheckJwksServerUrl(@QueryParameter String value) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class OicServerWellKnownConfiguration extends OicServerConfiguration {
private transient String userInfoServerUrl;
private transient boolean useRefreshTokens;
private transient TokenAuthMethod tokenAuthMethod;
private transient String issuer;

/**
* Time of the wellknown configuration expiration
Expand Down Expand Up @@ -77,6 +78,13 @@ public String getEndSessionUrl() {
return endSessionUrl;
}

@Override
@CheckForNull
public String getIssuer() {
loadWellKnownConfigIfNeeded();
return issuer;
}

@Override
public String getJwksServerUrl() {
loadWellKnownConfigIfNeeded();
Expand Down Expand Up @@ -158,6 +166,7 @@ private void loadWellKnownConfigIfNeeded() {
WellKnownOpenIDConfigurationResponse.class);

this.authorizationServerUrl = config.getAuthorizationEndpoint();
this.issuer = config.getIssuer();
this.tokenServerUrl = config.getTokenEndpoint();
this.jwksServerUrl = config.getJwksUri();
this.tokenAuthMethod = config.getPreferredTokenAuthMethod();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class WellKnownOpenIDConfigurationResponse extends GenericJson {
@Key("authorization_endpoint")
private String authorizationEndpoint;

@Key("issuer")
private String issuer;

@Key("token_endpoint")
private String tokenEndpoint;

Expand All @@ -42,6 +45,10 @@ public class WellKnownOpenIDConfigurationResponse extends GenericJson {
@Key("end_session_endpoint")
private String endSessionEndpoint;

public String getIssuer() {
return issuer;
}

public String getAuthorizationEndpoint() {
return authorizationEndpoint;
}
Expand Down Expand Up @@ -120,6 +127,9 @@ public boolean equals(Object o) {
if (!Objects.equal(authorizationEndpoint, obj.getAuthorizationEndpoint())) {
return false;
}
if (!Objects.equal(issuer, obj.getIssuer())) {

Check warning on line 130 in src/main/java/org/jenkinsci/plugins/oic/WellKnownOpenIDConfigurationResponse.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 130 is only partially covered, one branch is missing
return false;

Check warning on line 131 in src/main/java/org/jenkinsci/plugins/oic/WellKnownOpenIDConfigurationResponse.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 131 is not covered by tests
}
if (!Objects.equal(tokenEndpoint, obj.getTokenEndpoint())) {
return false;
}
Expand Down Expand Up @@ -148,6 +158,7 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
return (authorizationEndpoint
+ issuer
+ tokenAuthMethods
+ tokenEndpoint
+ userinfoEndpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ OicSecurityRealm.NotAValidURL = Not a valid url.
OicSecurityRealm.CouldNotRetreiveWellKnownConfig = Could not retrieve well-known config {0,number,integer} {1}
OicSecurityRealm.CouldNotParseResponse = Could not parse response
OicSecurityRealm.ErrorRetreivingWellKnownConfig = Error when retrieving well-known config
OicSecurityRealm.IssuerRecommended = For security reasons it is strongly recommended to provide an issuer.
OicSecurityRealm.TokenServerURLKeyRequired = Token Server Url Key is required.
OicSecurityRealm.TokenAuthMethodRequired = Token auth method is required.
OicSecurityRealm.UsingDefaultUsername = Using ''sub''.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
checked="${instance.tokenAuthMethod == null || instance.tokenAuthMethod == 'client_secret_post'}" value="client_secret_post" inline="true" help="${null}"/>
</f:entry>

<f:entry title="${%Issuer}" field="issuer">
<f:textbox/>
</f:entry>
<f:entry title="${%AuthorizationServerUrl}" field="authorizationServerUrl">
<f:textbox />
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
AuthorizationServerUrl=Authorization server url
Basic=Basic
EndSessionUrl=End session URL for OpenID Provider
Issuer=Issuer
JwksServerUrl=Jwks server url
Post=Post
Scopes=Scopes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Strongly recommended. The received ID Token's issuer must match the specified issuer.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Fortement recommandé. Le Token ID reçu doit avoir l'émetteur indiqué.
</div>
53 changes: 53 additions & 0 deletions src/test/java/org/jenkinsci/plugins/oic/PluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.Url;
import org.kohsuke.stapler.Stapler;
Expand Down Expand Up @@ -987,6 +988,58 @@ public void loginWithCheckTokenFailure() throws Exception {
assertAnonymous();
}

@Test
@Issue("SECURITY-3441")
public void loginWithIncorrectIssuerFails() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
jenkins.setSecurityRealm(
new TestRealm.Builder(wireMockRule).WithIssuer("another_issuer").build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
assertAnonymous();
}

@Test
@Issue("SECURITY-3441")
public void loginWithoutIssuerSetSucceeds() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
jenkins.setSecurityRealm(
new TestRealm.Builder(wireMockRule).WithIssuer(null).build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
assertTestUser();
}

@Test
@Issue("SECURITY-3441")
public void loginWithEmptyIssuerSetSucceeds() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
jenkins.setSecurityRealm(
new TestRealm.Builder(wireMockRule).WithIssuer(null).build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
assertTestUser();
}

@Test
@Issue("SECURITY-3441")
public void loginWithIncorrectAudienceFails() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
mockTokenReturnsIdTokenWithGroup();
jenkins.setSecurityRealm(new TestRealm.Builder(wireMockRule)
.WithClient("another_client_id", "client_secret").build());
assertAnonymous();
webClient.setThrowExceptionOnFailingStatusCode(false);
browseLoginPage();
assertAnonymous();
}

@Test
public void testAccessUsingJenkinsApiTokens() throws Exception {
mockAuthorizationRedirectsToFinishLogin();
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/org/jenkinsci/plugins/oic/TestRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class TestRealm extends OicSecurityRealm {
public static class Builder {
public String clientId = CLIENT_ID;
public Secret clientSecret = Secret.fromString("secret");
public String issuer = "issuer";
public String wellKnownOpenIDConfigurationUrl;
public String tokenServerUrl;
public String jwksServerUrl = null;
Expand Down Expand Up @@ -59,6 +60,11 @@ public Builder WithClient(String clientId, String clientSecret) {
return this;
}

public Builder WithIssuer(String issuer) {
this.issuer = issuer;
return this;
}

public Builder WithUserInfoServerUrl(String userInfoServerUrl) {
this.userInfoServerUrl = userInfoServerUrl;
return this;
Expand Down Expand Up @@ -139,6 +145,7 @@ public OicServerConfiguration buildServerConfiguration() {
}
conf.setJwksServerUrl(jwksServerUrl);
conf.setEndSessionUrl(endSessionEndpoint);
conf.setIssuer(issuer);
return conf;
} catch (Exception e) {
throw new IllegalArgumentException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class WellKnownOpenIDConfigurationResponseTest {
Set.of("token_endpoint_auth_methods_supported", "scopes_supported", "grant_types_supported");
private static final List<String> FIELDS = List.of(
"authorization_endpoint",
"issuer",
"token_endpoint",
"userinfo_endpoint",
"jwks_uri",
Expand Down

0 comments on commit 3afbfca

Please sign in to comment.