-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Refactor OIDC mint token endpoint #14063
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
Refactor OIDC mint token endpoint #14063
Conversation
0d2b3a5 to
933453d
Compare
tests/unit/oidc/test_views.py
Outdated
| publisher_name="fakepublishername", | ||
| publisher_url=lambda x=None: "https://fake/url", | ||
|
|
||
| publisher = github.GitHubPublisher( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to do this change because I had to add this change to stop linting errors from occurring here and anywhere else a prop was accessed on the OIDCPublisher object.
warehouse/oidc/models/_core.py
Outdated
| def publisher_url(self, claims=None) -> str | None: # pragma: no cover | ||
| def publisher_url( | ||
| self, claims: SignedClaims | None = None | ||
| ) -> str: # pragma: no cover |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been meaning to make this change for sometime so adding it to this PR so it's upstream sooner. This should never be None right? Or shouldn't, so types and tests should reflect that expectation? If that's not true, I'm happy to revert.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original intention here is that not all publishers would necessarily have a URL to link to, so this could be None, although that's not currently the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh ok. Then my change doesn't make sense. Will revert.
|
I wanted to address the comment here in this PR but I ended up breaking things up into the known route and the publisher we know that route handles. So, no looking at the request to see which publisher to load. Does this make sense? |
|
Ok, this is ready for review. @di, is it appropriate to ping you in that case? I've had a triple check look at the submitting patches docs but want to make sure I'm not bypassing a step. The actions on this repo keep you pretty locked in to that pages processes which is great. |
|
I noticed that quite a few of the tests in warehouse/tests/unit/oidc/test_views.py are implicitly testing the I went down a bit of a rabbithole to try to abstract things away a bit more in warehouse/tests/unit/oidc/test_views.py so that the new To achieve that I had to add two new factories to warehouse/tests/common/db/oidc.py,
Before I put in time to wrap up these changes, I wanted to run the idea of If I were able to make this change and introduce these new Factories, the only function in oidc/test_views.py that needs to know about a specific publisher is the If it made it easier to review and give feedback/answer this question, I could put up a PR that contains only the change to |
|
Morning @di, any thoughts on my question above? |
|
@di, if this PR overlaps with work you already have on the go, I don't mind dropping this PR. I should have asked before going this route in the first place. Let me know what your preference is. |
|
@th3coop I think this pairs nicely actually, let me get my branch up and we can figure out next steps. |
|
Works for me, @di. |
04fa637 to
626618f
Compare
84cdb92 to
84851d2
Compare
|
#14063 (comment) |
|
Hi @di, We're hoping to move this stuff along over the next few weeks. Any chance the branch you mentioned above is ready? |
84851d2 to
1700989
Compare
Make it easier to mint tokens with additional publishers
5a6d423 to
373d305
Compare
| id="fakeprojectid", | ||
| record_event=pretend.call_recorder(lambda **kw: None), | ||
| ) | ||
| publisher = pretend.stub( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def _find_publisher(claims, pending=False): | ||
| if pending: | ||
| raise errors.InvalidPublisherError | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is wrong I'm pretty sure but I'm not sure what to do about it.
I made this change because the coverage report was telling me that this line was never getting hit when the tests run.
This is true and correct because if I call oidc_service.find_publisher(claims, pending=False) and then I will NEVER get back anything but an OIDCPublisher, OR it will raise an exception.
The static type checking doesn't know that though, it just knows that OIDCPublisher | PendingOIDCPublisher will be returned.
The first thing that comes to mind to me is that _find_publisher shouldn't return two different types based on one of the inputs to the method. There should be a method per Class.
There are obviously other options but that's the first one that comes to mind for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this seems wrong, I think the line you're having trouble with is probably unnecessary, see the comment there.
| ] | ||
|
|
||
|
|
||
| def test_mint_token_from_oidc_only_pending_publisher_fail(monkeypatch, db_request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test tests an impossible state to reach as laid out in this comment and I'm not sure what to do about it.
|
Closing loops on this commend: I ended up following the existing pattern of stubbing away the code that shouldn't be tested, to focus on the code being test. So this new classes was removed from my changes. |
|
@di I saw your Google PR seems to have been merged which I believe is what I was waiting on for this PR so I've gone through this one again to get it ready for review. It's now ready for review. |
373d305 to
e750531
Compare
di
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @th3coop, thanks for your extreme patience here. I've given this a review and I think it's mostly good to go aside from some small comments. Thank you!
| request.response.status = 422 | ||
| return {"message": "Token request failed", "errors": errors} | ||
|
|
||
| def mint_token_from_oidc_github(request: Request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think instead we should probably make a generic non-GitHub-specific endpoint that accepts any OIDC token in the body and attempts to mint a token for it based on properties of the token (specifically, the iss claim should allow us to pick the right publisher type)
I don't see a great reason to maintain a separate endpoint for every OIDC provider we want to support here, that was probably not necessary in the GitHub implementation.
(cc @woodruffw for any thoughts as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agreed -- the main reason I did that originally was to avoid snooping the unverified JWT claims before verifying them, but that ship has sailed elsewhere in the implementation IIRC (and is not a security risk, so long as we perform proper key segregation and add both positive and negative tests).
We'll probably have to keep the GitHub-specific endpoint around for a while because of existing users (including some people who have reimplemented the token exchange flow), but it can be a thin shim over the "generic" endpoint 🙂
| if not isinstance(publisher, OIDCPublisher): | ||
| # This should be impossible, but we have to perform this type check to | ||
| # appease mypy otherwise we get type errors in the code after this | ||
| # point. | ||
| return _invalid( | ||
| errors=[ | ||
| { | ||
| "code": "invalid-publisher", | ||
| "description": "valid token, but no corresponding publisher", | ||
| } | ||
| ], | ||
| request=request, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems weird, does typing the find_publisher call fix this? Can we assert isinstance(publisher, OIDCPublisher) in the try/except instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this was icky. Good idea to use assert.
| def _find_publisher(claims, pending=False): | ||
| if pending: | ||
| raise errors.InvalidPublisherError | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this seems wrong, I think the line you're having trouble with is probably unnecessary, see the comment there.
|
@th3coop JFYI: I'm going to make a new PR off of your branch here, to address the comments/suggestions above. I'll preserve your history though, so it'll be your authorship in the merge 🙂 |
|
@di thanks for the review and no sweat. I appreciate any of your time! @woodruffw much appreciated! I was just about to get to this but thanks for taking it. Do you need anything from me in #15148? I really should have thought of using a tokens |
|
No problem! And nope, nothing needed at the moment -- I'll give you a ping there if I have any questions. Thanks a ton for your hard work here! |
|
Thanks @th3coop, I wasn't sure what your availability was and you've already put in a lot of work here so I asked @woodruffw to work on keeping this moving. We'd definitely appreciate you getting #13980 ready to merge once this/#15148 are merged if you're able! |
|
#15148 is undrafted, so closing here! |
|
No problem, I'll resolve the merge conflict in #13980. Clarification: #13980 is 1 of 3 PRs that add the full OIDC treatment for ActiveState. The other two are PRs in the ActiveState fork:
I'll create subsequent PRs against pypi/warehouse:main as the respective "parent" PR gets merged. Hopefully this makes sense for chunking up the changes. I didn't want to throw a PR at y'all with that includes ALL of the changes. |
* Refactor OIDC mint token endpoint Make it easier to mint tokens with additional publishers * Move default JWT validation to general OIDC handler function * publisher_name to method. Add more factories. Publisher agnostic token handler tests * oidc/views: typo: JsonRespone -> JsonResponse Signed-off-by: William Woodruff <[email protected]> * lintage, begin making OIDC endpoint generic Signed-off-by: William Woodruff <[email protected]> * tests, warehouse: generic OIDC minting endpoint Needs coverage, but the idea is done. Signed-off-by: William Woodruff <[email protected]> * tests: complete coverage Signed-off-by: William Woodruff <[email protected]> * tests, warehouse: eliminate impossible states Signed-off-by: William Woodruff <[email protected]> * tests, warehouse: feedback Signed-off-by: William Woodruff <[email protected]> * error specialization Signed-off-by: William Woodruff <[email protected]> --------- Signed-off-by: William Woodruff <[email protected]> Co-authored-by: Carey Hoffman <[email protected]>
Make it easier to mint tokens with additional publishers
This PR splits warehouse.oidc.view.mint_token_from_oidc into two functions. 1,
mint_token, is OIDC provider specific. The other,mint_token_from_oidc_*that is OIDC provider agnostic. This should make it easier to add more OIDC provider token handlers.ActiveState Internal work tracking ID: DS-1722