Skip to content
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

Plugin is calling getRootUrlFromRequest from outside a request handling thread. #506

Open
lsylvain opened this issue Jan 14, 2025 · 3 comments

Comments

@lsylvain
Copy link

Jenkins and plugins versions report

Environment
Jenkins version:
2.479.3

Plugins:
ant 511.v0a_a_1a_334f41b_
antisamy-markup-formatter 162.v0e6ec0fcfcf6
apache-httpcomponents-client-4-api 4.5.14-208.v438351942757
asm-api 9.7.1-97.v4cc844130d97
authentication-tokens 1.119.v50285141b_7e1
authorize-project 1.8.1
bootstrap5-api 5.3.3-1
bouncycastle-api 2.30.1.79-254.vfdb_814e7791e
branch-api 2.1206.vd9f35001c95c
build-timeout 1.33
caffeine-api 3.1.8-133.v17b_1ff2e0599
checks-api 2.2.1
cloudbees-folder 6.976.v4dc79fb_c458d
command-launcher 116.vd85919c54a_d6
commons-lang3-api 3.17.0-84.vb_b_938040b_078
commons-text-api 1.12.0-129.v99a_50df237f7
credentials 1405.vb_cda_74a_f8974
credentials-binding 687.v619cb_15e923f
depgraph-view 1.0.5
display-url-api 2.209.v582ed814ff2f
docker-commons 445.v6b_646c962a_94
docker-workflow 580.vc0c340686b_54
durable-task 581.v299a_5609d767
echarts-api 5.5.1-5
eddsa-api 0.3.0-4.v84c6f0f4969e
email-ext 1866.v14fa_6d201654
external-monitor-job 215.v2e88e894db_f8
font-awesome-api 6.6.0-2
git 5.7.0
git-client 6.1.0
github 1.40.0
github-api 1.321-478.vc9ce627ce001
github-branch-source 1809.v088b_5f22c768
gradle 2.14
gson-api 2.11.0-85.v1f4e87273c33
htmlpublisher 1.37
instance-identity 201.vd2a_b_5a_468a_a_6
ionicons-api 74.v93d5eb_813d5f
jackson2-api 2.17.0-379.v02de8ec9f64c
jakarta-activation-api 2.1.3-1
jakarta-mail-api 2.1.3-1
javadoc 280.v050b_5c849f69
javax-activation-api 1.2.0-7
javax-mail-api 1.6.2-10
jaxb 2.3.9-1
jjwt-api 0.11.5-112.ve82dfb_224b_a_d
joda-time-api 2.13.0-93.v9934da_29b_a_e9
jquery3-api 3.7.1-2
jquery-detached 1.2.1
json-api 20241224-119.va_dca_a_b_ea_7da_5
json-path-api 2.9.0-118.v7f23ed82a_8b_8
junit 1312.v1a_235a_b_94a_31
kubernetes 4306.vc91e951ea_eb_d
kubernetes-client-api 6.10.0-240.v57880ce8b_0b_2
kubernetes-credentials 190.v03c305394deb_
ldap 770.vb_455e934581a_
mailer 489.vd4b_25144138f
mapdb-api 1.0.9-40.v58107308b_7a_7
matrix-auth 3.2.3
matrix-project 840.v812f627cb_578
metrics 4.2.21-458.vcf496cb_839e4
mina-sshd-api-common 2.14.0-138.v6341ee58e1df
mina-sshd-api-core 2.14.0-138.v6341ee58e1df
oic-auth 4.452.v2849b_d3945fa_
okhttp-api 4.11.0-183.va_87fc7a_89810
pam-auth 1.11
pipeline-build-step 540.vb_e8849e1a_b_d8
pipeline-github-lib 61.v629f2cc41d83
pipeline-graph-analysis 216.vfd8b_ece330ca_
pipeline-groovy-lib 745.vdf6077913de0
pipeline-input-step 508.v584c0e9a_2177
pipeline-milestone-step 119.vdfdc43fc3b_9a_
pipeline-model-api 2.2218.v56d0cda_37c72
pipeline-model-definition 2.2218.v56d0cda_37c72
pipeline-model-extensions 2.2218.v56d0cda_37c72
pipeline-rest-api 2.34
pipeline-stage-step 312.v8cd10304c27a_
pipeline-stage-tags-metadata 2.2218.v56d0cda_37c72
pipeline-stage-view 2.34
plain-credentials 183.va_de8f1dd5a_2b_
plugin-util-api 5.1.0
resource-disposer 0.25
role-strategy 743.v142ea_b_d5f1d3
scm-api 698.v8e3b_c788f0a_6
script-security 1369.v9b_98a_4e95b_2d
snakeyaml-api 2.3-123.v13484c65210a_
sonar 2.17.3
ssh-credentials 349.vb_8b_6b_9709f5b_
sshd 3.330.vc866a_8389b_58
ssh-slaves 3.1021.va_cc11b_de26a_e
structs 338.v848422169819
subversion 1281.vc8837f91a_07a_
swarm 3.48
thinBackup 2.1.1
timestamper 1.28
token-macro 400.v35420b_922dcb_
trilead-api 2.147.vb_73cc728a_32e
variant 60.v7290fc0eb_b_cd
windows-slaves 1.8.1
workflow-aggregator 600.vb_57cdd26fdd7
workflow-api 1336.vee415d95c521
workflow-basic-steps 1058.vcb_fc1e3a_21a_9
workflow-cps 4007.vd705fc76a_34e
workflow-durable-task-step 1400.v7a_fd50a_091de
workflow-job 1476.v90f02a_225559
workflow-multibranch 795.ve0cb_1f45ca_9a_
workflow-scm-step 427.v4ca_6512e7df1
workflow-step-api 678.v3ee58b_469476
workflow-support 936.v9fa_77211ca_e1
ws-cleanup 0.48

What Operating System are you using (both controller, and any agents involved in the problem)?

I'm using the bitnami jenkins container image: bitnami/jenkins:2.479.3-debian-r0

From inside the container image cat /etc/os-release confirms Debian 12 (bookworm)

Reproduction steps

  1. Run Jenkins.
  2. Configure the oic-auth plugin to authenticate. I happen to be using keycloak, but this error will occur regardsless of the OIDC provider.
  3. Move around in the UI. One thing that will help is to select Manage Jenkins/plugins.
  4. You may need to go back and forth a few times, but after a short while the error will occur.

Expected Results

The UI behaves as expected.

Actual Results

The message "A problem occurred whil processing the request" displays.

If you check the Jenkins logs and you will find the following:

java.lang.IllegalStateException: cannot call getRootUrlFromRequest from outisde a request handling thread
at jenkins.model.Jenkins.getRootUrlFromRequest(Jenkins.java:2574)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.getRootUrl(OicSecurityRealm.java:1251)

Anything else?

I've tried Jenkins versions 2.479.1 and 2.479.3 with the same result. I verified that the oic-auth.jpi plugin is the latest available.

Are you interested in contributing a fix?

I don't know how much time I have available, but I can take a look and will give you a heads up if I find anything in the code. I believe the get getRootUrlFromRequest call is being made from outside the scope of the request context. The code may need to be refactored to address this.

@lsylvain
Copy link
Author

lsylvain commented Jan 14, 2025

Looking at the oic-auth-plugin code, it appears the problem starts in org.jenkinsci.plugins.oic.OicSecurityRealm.java here:

   private boolean refreshExpiredToken(
            String expectedUsername,
            OicCredentials credentials,
            HttpServletRequest httpRequest,
            HttpServletResponse httpResponse)
            throws IOException {

        FrameworkParameters parameters = new JEEFrameworkParameters(httpRequest, httpResponse);
        WebContext webContext = JEEContextFactory.INSTANCE.newContext(parameters);
        SessionStore sessionStore = JEESessionStoreFactory.INSTANCE.newSessionStore(parameters);
        OidcClient client = buildOidcClient();

The refreshExpiredToken method receives the HttpServletRequest as a parameter but does not use it when calling buildOidcClient, which in turn calls buildOAuthRedirectUrl, which calls ensureRootUrl, which calls getRootUrl(), which finally calls Jenkins.get().getRootUrlFromRequest(). The getRootUrlFromRequest method in the jenkins.model.Jenkins class automatically throws a java.lang.IllegalStateException if there is no HttpServletRequest in context, as shown below.

   /**
     * Gets the absolute URL of Jenkins, such as {@code http://localhost/jenkins/}.
     *
     * <p>
     * This method first tries to use the manually configured value, then
     * fall back to {@link #getRootUrlFromRequest}.
     * It is done in this order so that it can work correctly even in the face
     * of a reverse proxy.
     *
     * @return {@code null} if this parameter is not configured by the user and the calling thread is not in an HTTP request;
     *                      otherwise the returned URL will always have the trailing {@code /}
     * @throws IllegalStateException {@link JenkinsLocationConfiguration} cannot be retrieved.
     *                      Jenkins instance may be not ready, or there is an extension loading glitch.
     * @since 1.66
     * @see <a href="https://wiki.jenkins-ci.org/display/JENKINS/Hyperlinks+in+HTML">Hyperlinks in HTML</a>
     */
    public @NonNull String getRootUrlFromRequest() {
        StaplerRequest2 req = Stapler.getCurrentRequest2();
        if (req == null) {
            throw new IllegalStateException("cannot call getRootUrlFromRequest from outside a request handling thread");
        }

At first glance, it appears that the solution is to refactor the doFilter, handleTokenExpiration, refreshExpiredToken, buildOidcClient, buildOAuthRedirectUrl, ensureRootUrl and getRootUrl methods of the org.jenkinsci.plugins.oic.OicSecurityRealm class to accept and use an HttpServletRequest parameter.

@jtnord
Copy link
Member

jtnord commented Jan 15, 2025

The message "A problem occurred whil processing the request" displays

Displays where?

If this displays on the UI then that would be strange as a UI request can not by it's nature not have a request.

Is there anymore in the stacktrace?

@lsylvain
Copy link
Author

lsylvain commented Jan 16, 2025

Unfortunately I am deployed in an airgapped environment with no direct Internet access and unable to readily copy-and-paste data here for security reasons, but here is the top of the call stack I manually entered from a stacktrace collected in the airgapped ennvironment.

java.lang.IllegalStateException: cannot call getRootUrlFromRequest from outside a request handling thread
at jenkins.model.Jenkins.getRoolUrlFromRequest (Jenkins.java: 2574)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.getRootUrl (OicSecurityRealm.java: 1251)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.ensureRootUrl (OicSecurityRealm.java: 1258)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.buildOAuthRedirectUrl (OicSecurityRealm.java: 1271)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.buildOidcClient (OicSecurityRealm.java: 700)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.refreshExpiredToken (OicSecurityRealm.java: 1429)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.handleTokenExpiration (OicSecurityRealm.java: 1390)
at PluginClassLoader for oic-auth//org.jenkinsci.plugins.oic.OicSecurityRealm.doFilter (OicSecurityRealm.java: 878)
at hudson.security.ChainedServletFilt2$1.doFilter(ChainedServletFilter2.java: 99)

You are right that there must be a request object for each request. If you follow through the above call stack you will see the problem is the call to getRootUrlFromRequest is from outside a request handling thread, most likely because the following method in the Jenkins class was never called:

public void doFinishLogin(StaplerRequest2 request, StaplerResponse2 response) throws IOException, ParseException {
        OidcClient client = buildOidcClient();

An additional note is that the behavior only occurs sporadically, which smells like a concurrency issue. Clearly there are some cases where the StaplerRequest2 has not been initialized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants