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

SNOW-1825621 OAuth code flow support #2135

Open
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

sfc-gh-mkeller
Copy link
Collaborator

@sfc-gh-mkeller sfc-gh-mkeller commented Jan 14, 2025

Please answer these questions before submitting your pull requests. Thanks!

  1. What GitHub issue is this PR addressing? Make sure that there is an accompanying issue to your PR.

    Fixes SNOW-1825621

  2. Fill out the following pre-review checklist:

    • I am adding a new automated test(s) to verify correctness of my new code
    • I am adding new logging messages
    • I am adding a new telemetry message
    • I am modifying authorization mechanisms
    • I am adding new credentials
    • I am modifying OCSP code
    • I am adding a new dependency
  3. Please describe how your code solves the related issue.

    In this PR I add support for OUath authorization code flow. It's very similar to external browser authentication, but it uses oauth and more options are more better for users.
    This change has been tested manually, as it's fairly complicated to setup and we don't do unit tests for the different authentication methods.

  4. (Optional) PR for stored-proc connector: Not applicable

@sfc-gh-mkeller sfc-gh-mkeller added the DO_NOT_PORT_CHANGES_TO_SP Add this label when changes in this PR do not need to be port to SP connector label Jan 14, 2025
@sfc-gh-mkeller sfc-gh-mkeller self-assigned this Jan 14, 2025
Copy link
Contributor

@sfc-gh-eworoshow sfc-gh-eworoshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

A couple over-arching thoughts:

  • I only really looked at the security semantics, we should have additional reviewers for the Pythonic HTTP bits.
  • Ideally we find some way to add automated tests for this flow. Do we have browser automation testing, for example, that we could use to build an integration test?

src/snowflake/connector/auth/_http_server.py Show resolved Hide resolved
src/snowflake/connector/auth/_http_server.py Show resolved Hide resolved
logger = logging.getLogger(__name__)


class AuthHttpServer:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume we have this to avoid taking additional dependencies in the driver?

(As we move to introduce additional forms of CSP-specific authentication I suspect we'll want to introduce dependencies on the various CSP libraries, which is in part why I ask.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I copied this code all from externalbrowser auth. So it's tried and tested given that I have made no mistakes. I didn't feel like I needed to introduce any dependencies for this OAuth implementation and I'm not sure I'd have time to really investigate different options anymore. I'd like to leave this up to the new owners of the library if you don't mind

src/snowflake/connector/connection.py Outdated Show resolved Hide resolved
src/snowflake/connector/connection.py Outdated Show resolved Hide resolved
src/snowflake/connector/auth/oauth_code.py Outdated Show resolved Hide resolved
src/snowflake/connector/auth/oauth_code.py Outdated Show resolved Hide resolved
src/snowflake/connector/auth/oauth_code.py Show resolved Hide resolved
fields=fields,
)
try:
self._oauth_token = json.loads(resp.data)["access_token"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at some point we'll also have to content with refresh_token coming back, I expect

can the driver be used to establish multiple sessions using the same credentials? (if so we might also need to handle the case where the access token has expired and we need to retrieve a new one, either via the refresh token if it's available or via the full browser flow)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it cannot. Users can manually feed the same credentials to the connector, but we discard the information required to create a session in reset_secrets method once we consume them.

":".join(origin_line.split(":")[1:]).strip(),
)

def _process_options(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a lot of mechanics shared with the external browser flow that I wonder might be better hoisted somewhere common?

Copy link
Collaborator Author

@sfc-gh-mkeller sfc-gh-mkeller Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I started sharing code between the two, but I didn't plan on touching externalbrowser code in this PR. I'd like to touch that in the near future. Once this code is verified to work properly. I've filed a follow-up Jira for this effort. If you would like me to close do this in this PR please let me know, but otherwise we could pick this up in a bit.

@sfc-gh-mkeller
Copy link
Collaborator Author

Do we have browser automation testing, for example, that we could use to build an integration test?

We do this manually when we mess with the authentication code, otherwise we just don't touch these pieces of the code and we don't test them. None of them have any dependencies outside of std lib, so there's no need to

Copy link
Contributor

@sfc-gh-eworoshow sfc-gh-eworoshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broadly LGTM modulo a few minor comments!

We should have the "new owners" take a look too. This is complex and sensitive enough that I think it benefits from additional eyes.

src/snowflake/connector/auth/oauth_code.py Outdated Show resolved Hide resolved
src/snowflake/connector/auth/oauth_code.py Show resolved Hide resolved
src/snowflake/connector/auth/oauth_code.py Outdated Show resolved Hide resolved
Copy link
Contributor

@sfc-gh-eworoshow sfc-gh-eworoshow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Stamped.

I'd really love more eyes on this, however. Can we perhaps involve the team taking over this driver to take a second look? There's enough security-sensitive handling here that more than one reviewer is ~necessary.

"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
}
if self.client_secret:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still expecting that we'll remove this from the body since it appears in the header?

Copy link
Collaborator

@sfc-gh-jszczerbinski sfc-gh-jszczerbinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide some testing? I think it can be done pretty easily. We could inject fake/mock webbrowser object and access token request? The fake web browser could do a redirect call immediately (extracted from the url).

src/snowflake/connector/auth/oauth_code.py Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DO_NOT_PORT_CHANGES_TO_SP Add this label when changes in this PR do not need to be port to SP connector
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants