I'm not sure if it is container related, however it seems that the CorsFilter is not picked up automatically although the @WebFilter("/*") should do that:
@WebFilter("/*")
public class CorsFilter implements Filter
Instead I had to manually add to web.xml this:
<filter>
<filter-name>edu.harvard.iq.dataverse.filter.CorsFilter</filter-name>
<filter-class>edu.harvard.iq.dataverse.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>edu.harvard.iq.dataverse.filter.CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I also checked in the debugger and without the explicit web.xml doFilter() is not get called.
This topic was moved here from #containers > CorsFilter problem by Oliver Bertuch.
As this is not purely container related, I moved this here @Balázs Pataki
Are there any API tests for the filter?
It would be good to have a reproducer.
I just tried calling a DV endpoint from a browser component, but try to come up with a curl.
It would be good to have an API test included in our regular suite, so we can also test this is in CI
Actually, I don't know how to make curl fail with CORS, because it never does as it doesn't care, only browsers do, right? For now I just vibe coded a simple single page app which tries to create a dataset with POST and it does fail for me with stock develop branch DV (without manually adding the filter to webxml):
Just open it in a browser, set the api token and press "POST JSON" to see if it fails or not. (It uses the dataset-finch1.json from the DV API guides)
I was suggesting creating an IT test that checks for presence of the headers.
(As you said - anything that's not a browser won't care. But we'd only want to verify the servlet filter is picked up)
Ok, I created a CorsIT test checking for CORS headers.
It turns out that the result is kind of random based on whether the server happens to pick up CorsFilter or not.
Here's my actual test session: I run the app in docker. When it is started I run the test manually. Then I stop the server with ctrl+c and either start it again with run, or first do an explicit stop. None of these seem to matter, the result seems to be random:
mvn -Pct docker:stop
mvn -Pct docker:run --> FAIL
ctrl+c
mvn -Pct docker:run --> FAIL
ctrl+c
mvn -Pct docker:stop
mvn -Pct docker:run --> PASS
ctrl+c
mvn -Pct docker:run --> PASS
ctrl+c
mvn -Pct docker:run --> FAIL
I don't know how to proceed from here. One thing is sure: If add the explicit web.xml filter and filter-mapping it works consistently.
This is the test I run:
In the docker-compose-dev.yml I set these for dev_dataverse just to see if I get specific headers I configure:
DATAVERSE_CORS_ORIGIN: "*"
DATAVERSE_CORS_HEADERS_ALLOW: "Accept,Content-Type,X-Dataverse-key,Authorization,cedar-client-session-id,cedar-debug"
``
In your test I see that you are targeting an API endpoint.
Yes, /api/dataverses/root/datasets
Are we sure the filter actually will correctly work with JAX-RS
What happens when hitting JSF or other pure servlets?
Yes, sometimes it does, sometimes it doesn't. I don't know what kind of race condition could
it be.
What happens if we hit a 404 in a servlet
I don't know about JSF, but I want to access api endpoints from a webapp
What url do you suggest to test?
Technically, JAX-RS is still a servlet, but it's special. We may be in trouble here due to filter ordering etc.
Also, AI tells me there may be a problem with the mixed style of having web.xml registered filters and annotation based. Not sure if this really is a thing, but lets keep it in mind.
Yes, I think it definitely affects it, although as I read about it should be still deterministic
SwordFilter is the only one configured in web.xml. Maybe removing it from there and adding @WebFilter annotation on the class makes it clear.
I try this.
Technically for JAX-RS we should be using @ContainerRequestFilter to be within the consistent filter framework of JAX-RS
See ApiBlockingFilter in api.filter package.
So this may be another culprit, why things my go sideways - the servlet filter and the JAX-RS filter may conflict.
At least if both are discovered by annotations scanning.
Maybe making the filter explicit in web.xml makes it load before the other or sth
Hmm coming to think about it: any servlet filters will be applied first, then the JAX-RS internal filters will apply. These two don't mix-n-match, they are at different layers. So should be nothing to worry about here.
@Balázs Pataki could you please verify something for me...
Yes, sure.
Could you run the tests against /api/v1/...
I had a look at all the filters we already have and ApiRouter popped up. Asking AI about it gave me this:
The
ApiRouteris doing an internalRequestDispatcher.forward(...)from:
/api/...→/api/v1/...(your JAX‑RS app is mounted at )@ApplicationPath("api/v1")That has a big side effect in Servlet-land: a forward is a second dispatch with dispatcher type FORWARD, and many servlet filters only apply to dispatcher type REQUEST unless explicitly configured.
So let's rule out that the router may mess up our filter chain
In the meantime I tested putting @WebFilter onto SwordFilter and removing the web.xml config --> same random test behaviour
I now try the /api/v1 behaviour
Great to verify that! We doubted it would be related, glad to see it was indeed not.
My money is on Router vs Cors, as both affect the same endpoints.
Also, you might want to do the testing with the sword filter as well :wink: Not sure if the order matters with those two. IMHO filters should avoid being dependent on order. But of course we'd need to take care of types (forward, request)
Yep. So, when /api/dataverses/root/datasets fails /api/v1/dataverses/root/datasets passes the test.
Now what? :thinking:
In case of /api/v1 it first reaches CorsFilter then ApiBlockingFilter
In case of /api only reaches ApiBlockingFilter
(doFilter(), I mean)
Ha! So Api Router is the culprit
But you're somewhat wrong. The chain is:
/api -> Router -> forward -> Cors -> ApiBlocking (which breaks at Cors because forward)
/api/v1 -> Router -> Cors -> ApiBlocking
Maybe that's how it should be, but no, in case of /api Cors is not ALWAYS reached.
So in case of
RequestDispatcher dsp = request.getRequestDispatcher(newRequestUri);
dsp.forward(req, sr1);
wherever it goes it won't always goes thro CorsFilter, it is not in that filter chain, or sg.
So what we need to do: add both "FORWARD" and "REQUEST" to CorsFilter:
@WebFilter(value = "/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
We should also look at the types ERROR and INCLUDE later on to avoid breaking CORS when serving error pages etc
Balázs Pataki said:
Maybe that's how it should be, but no, in case of /api Cors is not ALWAYS reached.
It goes there, but will not match because of dispatcher type forward!
Ok, I check now adding DispatcherType.FORWARD
Please make sure to use the above example, otherwise we'd break REQUEST :wink:
yeah, i copy pasted that :-)
Oh yeah, it works consistently now! :tada:
And the filter orders are as follows:
/api
cors -> router -> cors -> apiblocking
/api/v1
cors -> router -> apiblocking
Good morning. :coffee: You've been busy!
Submitted a PR:
https://github.com/IQSS/dataverse/pull/12151
Should we add anything else to it, @Oliver Bertuch ?
Can you test the error pages?
And SWORD maybe?
You mean to add more URL-s to test in CorsIT, right?
Yeah, lets have more tests in there
Might be an @Parameterized one or single ones.
Not sure we can make RestAssured hit the other endpoints that easily.
@Don Sizemore this may be worth another backport to 6.9
I made it ParameterizedTest and now it tests these endpoints
"/api/dataverses/root/datasets",
"/api/v1/dataverses/root/datasets",
"/page_doesnt_exist",
"/dvn/api/data-deposit/v1.1/swordv2/collection/dataverse/root"
You probably should add CorsIT to the list of tests at https://github.com/IQSS/dataverse/blob/develop/tests/integration-tests.txt
I've always meant to refactor the API tests to properly use Maven Failsafe and JUnit 5 Suites, but didn't find the time so far...
I left two comments on the PR
Edit: added another one.
@Balázs Pataki I've marked your PR as a draft for now and added all the necessary labels (put didn't put it on the board yet @Philip Durbin 🚀). Keep them changes coming, I'll be the ambassador on including it in 6.10
I'd say we can safely put it in the "Ready for Triage" column so it comes up on Triage Tuesday. Any objections? Thanks for the PR! :heart:
@Balázs Pataki @Oliver Bertuch I'm reviewing #12151 and I rebased the branch. I hope you don't mind.
@Oliver Bertuch I added a note about requirements.txt changes that doc writers will need to know about.
@Don Sizemore tests are failing on Jenkins. Please see https://github.com/IQSS/dataverse/pull/12151#discussion_r3030000487
Should we enable CORS on the Dataverse instances Jenkins spins up? Can I co-assign you to the PR until we decide how to handle this?
@Balázs Pataki @Oliver Bertuch I have questions about #12151 and left reviews for you.
Hello! I am working on Dataverse Ansible to add in the CORS fix to make the CorsIT tests pass.
I was able to allow CORS from DA's side, but I kept running into this issue when testing:
Time elapsed: 0.011 s <<< FAILURE!\norg.opentest4j.AssertionFailedError: Unexpected value for header: Access-Control-Allow-Headers ==> expected: <[range, content-type, x-dataverse-key, accept]> but was: <[*]>\n\
For now, I'm trying to change the Access Control Allow Headers to those exact strings to make sure the tests pass, but I am of the opinion that the assertions are a bit rigid
@Ash Manda you don't permission to change the test in the PR, right? You'd need the author, @Balázs Pataki, to do that.
Nope, I can't change it myself.
Right. Could you please go ahead and leave a review or a comment on the line you're talking about? So @Balázs Pataki can easily find it.
Done!
https://jenkins.dataverse.org/job/IQSS-Dataverse-Develop-PR/job/PR-12151/16/testReport/ is passing now! Thanks, @Ash Manda and @Don Sizemore!
@Balázs Pataki @Oliver Bertuch is CORS on by default now? I'm confused by these docs: https://github.com/IQSS/dataverse/pull/12151#discussion_r3030369558
@Philip Durbin 🚀 Hi Phil. I see you've approved the CORSfilter PR which is amazing! I just wanted to add that I would still strongly recommend that the assertions for the CorsIT test be rewritten to support "*" and extended headers, before the PR is merged as it makes it very difficult during testing to figure out what exact CORS strings need to be allowed to make the tests pass as giving any additional headers will just fail it.
It should be just 4-5 lines of code change at max, mostly regexes and it makes life easier for everyone involved at the CORS side.
Or you could just merge the PR, and I can open a separate issue later on for a minor fix if you would prefer that.
Thanks!
@Ash Manda good point! I did see your "request changes" review but I wasn't sure how strongly you feel about it!
@Balázs Pataki or @Oliver Bertuch are you interested in making the changes above?
I think it might be best to go ahead with the PR merge for now. I'll open a minor fix PR later on for the test failures.
I had another look today. Currently, the tests simply expect the default values for the Access-Control-Allow-Headers and Access-Control-Expose-Headers when these settings are not configured.
It sounds like for the Jenkins setup, you're setting them both to "*"
I understand that y'all want to be cautious with these options, as they may have side effects.
To test this in a cleaner fashion, we have a few options....
Environment isolation: deploy a separate test instance just for this test.
- This may be hard in the current Jenkins setup.
- It's easier with containerized tests, but still a lot.
- It would still be overhead but at least easily controlled if we'd create these containers with Testcontainers from within the test.
- Using a separate environment neatly solves the problem that the filter is initialized only once with the values. Changes need a redeploy.
Variation of (1): make it an "integration test", not an API End to End test. We already have some of those using Testcontainers. We can use the packaged container to run Dataverse in it. We'd need some more infrastructure though to setup the Dataverse deployment in it.
My favorite is (3) as it is the most sustainable effort, not just for CORS tests. It goes well beyond testing, too. The application would have a whole new feature being more "hot reloadable".
Maybe we should make the test be less opinionated. The test was written to make sure that the CORS headers are present on each of the different endpoints served by different subsystems.
So instead of asserting the content of the headers, asserting the presence of the headers would be good enough for now.
We can move the content assertions to a different test that is @Disabled for now until we resolve the tech debt I mentioned above.
Hi Oliver. I appreciate the detailed reply! From my side, I am being persistent about this because I just want "*" and even lassing additional headers to be considered a valid assertion for the test, pretty much. I am not asking for a rewrite or anything. I dont want to create anya additional load than you already have
Oliver Bertuch said:
So
Yes, that is exactly what I meant! Thank you! You put it a lot better than I ever could ;)
I'll change the test now.
Done.
I left a whole bunch of Javadocs on the test to explain
Please let me know how Jenkins is doing :smile:
Tysm! I'll let you know immediately after the build finishes. Could be an hour or so from now.
You folks have been busy! @Oliver Bertuch thanks for the commit at https://github.com/IQSS/dataverse/pull/12151/changes/ed07f964cb02bd8cc89e6daa46ff57ecf0fe5c84 . I'm taking a look.
Unfortunately, from here at home, I can't see the details of the Jenkins test run but all the checks in #12151 are green, at least, which is a good sign.
I think the commit makes sense to me. Just looking for the presence of headers. Seems fine. And future work is explained in a comment. @Ash Manda what do you think?
@Oliver Bertuch Jenkins looking good, all tests passing
Philip Durbin 🚀 said:
I think the commit makes sense to me. Just looking for the presence of headers. Seems fine. And future work is explained in a comment. Ash Manda what do you think?
Yep, sounds good to me!
thank you all for your work on this even though it was supposed to be a small change
Thank you @Oliver Bertuch for solving this!
Woohoo! Ship it! :ship: :wink:
No worries @Balázs Pataki. It was a small thing to do once the wrapping around the head was done @Ash Manda :see_no_evil:
Ash Manda said:
Oliver Bertuch Jenkins looking good, all tests passing
Yes! API tests took 22 minutes: https://jenkins.dataverse.org/job/IQSS-Dataverse-Develop-PR/job/PR-12151/18/testReport/
@Ash Manda do you want to formally remove your "requested changes" status? Alternatively, I can click "dismiss review":
![]()
I did go ahead and move it to "ready for QA" column of the project board.
Hi Phil, you can go ahead and dismiss it!
Done! https://github.com/IQSS/dataverse/pull/12151#event-24571051576
_
.-'` `}
_./) / } SHIP IT! :-)
.'o \ | }
'.___.'`.\ {`
/`\_/ , `. }
\=' .-' _`\ {
`'`;/ `, }
_\ ; }
/__`;-...'--'
Last updated: May 30 2026 at 09:11 UTC