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

Allow a cors custom validation function which receives the full gin context #140

Merged
merged 12 commits into from
Mar 10, 2024

Conversation

dbhoot
Copy link
Contributor

@dbhoot dbhoot commented Feb 23, 2024

Problem

You cannot add separate cors config / middleware at the router group level. While adding the middleware works just fine, when a browser executes the pre-flight request, gin will return a 404.

This is because gin checks the existence of a route before running any middlewares on that router group. However, middlewares at the engine level always run. This means you have to apply the cors middle at the global/engine level and cannot have separate configs for different sections of your backend.

The Cors config allows a custom origin validation function. However, that function only receives the origin header.

Example

api.example.com/app1 only allows origins app1.example.com
api.example.com/app2 only allows origins from app2.example.com
api.example.com/v1 allows all origins

This would not be possible in the current cors configuration options.

Solution

Allow the config to specify an origin validation function which also receives the entire gin context.

@dbhoot dbhoot changed the title Feat/allow new validation func Allow a cors custom validation function which receives the full gin context Feb 23, 2024
@jub0bs
Copy link

jub0bs commented Feb 27, 2024

@dbhoot As proposed, this feature is dangerous. Since users of AllowOriginWithContextFunc would have access to the entire request, you have to worry about cache poisoning. See this related issue in rs/cors.

@jub0bs
Copy link

jub0bs commented Feb 28, 2024

Related: #67

@dbhoot
Copy link
Contributor Author

dbhoot commented Feb 29, 2024

@jub0bs If I understand the poisoning issue, #60 was problematic because it didn't have access to the response. Without access to the response there would be no way way to set the Vary header.

With the full context, you are able to do so, but I think it's quite bad to do that in the AllowOriginWithContext function. A correctly written allow origin function shouldn't have side effects -- you could make it work but it would be kinda ugly.

Currently, the package only sets the Vary header with origin when you're allowing all origins.

Perhaps, if you have an allow origin function with context, you have to also specify a custom function on the config which will set the Vary header. I'm happy to add that and update the tests, readme, etc.

I do think it makes good sense to allow an origin validation with context so I'm open to suggestions on how to add the capability safely.

@jub0bs
Copy link

jub0bs commented Mar 1, 2024

@dbhoot

With the full context, you are able to do so [...]

That's true. But would average users think of adequately populating the Vary header? Doubftul. I still believe that callbacks for configuring CORS are a footgun. They're powerful, but hard to use, and much too easy to misuse.

I'm curious about your use case for AllowOriginWithContextFunc. Could you clarify? On what elements of the request would you like to base the boolean result? The test case that you've added doesn't justify the need for AllowOriginWithContextFunc:

AllowOriginWithContextFunc: func(c *gin.Context, origin string) bool {
  return origin == "http://sample.com"
},

AllowOrigins: []string{"http://sample.com"} would be enough for this.

@codecov-commenter
Copy link

codecov-commenter commented Mar 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.87%. Comparing base (2451987) to head (063635d).
Report is 7 commits behind head on master.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #140      +/-   ##
==========================================
- Coverage   95.11%   94.87%   -0.24%     
==========================================
  Files           3        3              
  Lines         225      195      -30     
==========================================
- Hits          214      185      -29     
+ Misses         11       10       -1     
Flag Coverage Δ
go- 94.87% <100.00%> (-0.24%) ⬇️
go-1.18 94.87% <100.00%> (-0.24%) ⬇️
go-1.19 94.87% <100.00%> (-0.24%) ⬇️
go-1.20 94.87% <100.00%> (-0.24%) ⬇️
go-1.21 94.87% <100.00%> (-0.24%) ⬇️
go-1.22 94.87% <100.00%> (-0.24%) ⬇️
macos-latest 94.87% <100.00%> (-0.24%) ⬇️
ubuntu-latest 94.87% <100.00%> (-0.24%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dbhoot
Copy link
Contributor Author

dbhoot commented Mar 1, 2024

@jub0bs

I'm curious about your use case for AllowOriginWithContextFunc. Could you clarify? On what elements of the request would you like to base the boolean result? The test case that you've added doesn't justify the need for AllowOriginWithContextFunc:

I want the path. Allowing example.com doesn't work because

  1. The subdomains are relevant. All of the resources in /app1 should only be consumed from origin app1.example.com. Same for the second app.
  2. The third url namespace in the example allows all origins

In my example, I initially tried setting a separate cors middleware at each router group level. However, the pre-flight requests fail as I mentioned in the description.

You're right that a developer would probably not know to populate the Vary header by default. I only learned from the links you shared. Now that I know, I would be able to populate it correctly.

I do think my suggestion of a custom function on the configuration to set it would solve the problem. I can add that if you think it reasonably solves the problem. As I mentioned before, I'm also open to other ideas. My particular use does not require it but it does seem necessary in certain configurations.

Regarding footguns: my take is that it is important to provide a way to do necessary things while educating about the risks and providing examples rather than being very restrictive.

Consider the http standard already allows headers to be in the cache key for a cors response. That means there are already legit use cases in the wild which are fully supported in the http standard which are impossible to configure using this package. Adding the suggested function on the config would address that concern as well.

From another perspective: does it make sense to completely block valid and supported uses entirely because a developer may make a mistake or is it better to support it and show people how to do it correctly?

@jub0bs
Copy link

jub0bs commented Mar 1, 2024

@dbhoot

I want the path. [...] I initially tried setting a separate cors middleware at each router group level. However, the pre-flight requests fail as I mentioned in the description.

I think I understand the problem, but bear in mind that it stems from the design of Gin's router. There's no such issue with net/http's ServeMux, even with the new method-full patterns supported in Go 1.22. See, for instance, golang/go#61410 (comment)

You're right that a developer would probably not know to populate the Vary header by default. I only learned from the links you shared.

That's what I'm worried about. Cache poisoning can have serious implications, yet most people are not aware of them.

Regarding footguns [...]

If you follow this rationale to the end, you might as well let people implement their own CORS middleware. In my opinion, a good library should be easy to use but also (!) hard to misuse. But the final decision about this PR doesn't rest with me, obviously.

@dbhoot
Copy link
Contributor Author

dbhoot commented Mar 10, 2024

@jub0bs -- I agree -- changing the router could also work. That could also have risks though since there may be people who depend on the way it currently works. Figured this would be the safer and quicker win.

In my opinion, a good library should be easy to use but also (!) hard to misuse
No argument from me.

@appleboy I see I'm triggering linting with lines longer than 80 characters. I'm happy to shorten those but I notice that there are many existing lines which violate the linting rule. Are you expecting to address everything in the entire module?

@appleboy
Copy link
Member

@dbhoot I found that only this PR violates this rule, so it can be fixed directly.

@dbhoot
Copy link
Contributor Author

dbhoot commented Mar 10, 2024

@dbhoot I found that only this PR violates this rule, so it can be fixed directly.

Ok, that's good! I just pushed an update to the error string. Hopefully that's the last linting error.

cors_test.go Show resolved Hide resolved
@dbhoot dbhoot requested a review from appleboy March 10, 2024 06:22
@appleboy appleboy merged commit 9d49f16 into gin-contrib:master Mar 10, 2024
8 of 9 checks passed
@appleboy
Copy link
Member

@dbhoot @jub0bs Thanks.

@appleboy
Copy link
Member

@dbhoot I have updated the lint check in 4447aeb Thanks for your time.

@appleboy
Copy link
Member

@dbhoot dbhoot deleted the feat/allow-new-validation-func branch March 10, 2024 07:07
@dbhoot
Copy link
Contributor Author

dbhoot commented Mar 10, 2024

@appleboy thank you for the review and assist on the linting. 🙏

@jub0bs
Copy link

jub0bs commented Mar 10, 2024

Another reason why I'm opposed to this new callback feature (aside from the fact that it's a footgun) is that, if it's justified by the need to inspect the request's URL/path, then you have poor separation of concerns: the path is indeed processed both by the router and by the CORS middleware.

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

Successfully merging this pull request may close these issues.

4 participants