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

Add typing and async support for SSO #291

Merged

Conversation

mattgd
Copy link
Contributor

@mattgd mattgd commented Jul 25, 2024

Description

Add typing and async support for SSO.

Documentation

Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.

[ ] Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

@mattgd mattgd self-assigned this Jul 25, 2024
Copy link

linear bot commented Jul 25, 2024

@mattgd mattgd force-pushed the feature/dsync-1735-add-types-to-connection-api-methods branch from 55dbdaf to d0130fe Compare July 25, 2024 16:30
@mattgd mattgd marked this pull request as ready for review July 25, 2024 16:31
@mattgd mattgd requested a review from a team as a code owner July 25, 2024 16:31
@mattgd mattgd requested review from amygdalama and tribble and removed request for a team July 25, 2024 16:31
Copy link

@tribble tribble left a comment

Choose a reason for hiding this comment

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

See comments inline. Creating an HTTP client to generate a URL seems like such a smell. Is there a better way?

@pytest.fixture
def mock_profile(self):
return {
"object": "profile",
Copy link

Choose a reason for hiding this comment

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

Kind of amazing how invalid our test fixtures were.

connection_list = [MockConnection(id=str(i)).to_dict() for i in range(10)]

return {
"object": "list",
Copy link

Choose a reason for hiding this comment

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

I did create a little list_response_of helper to help with this repetition

self.customer_domain = "workos.com"
self.login_hint = "[email protected]"
self.redirect_uri = "https://localhost/auth/callback"
self.state = json.dumps({"things": "with_stuff"})
Copy link

Choose a reason for hiding this comment

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

Should this correspond to a Connection state?

Copy link

Choose a reason for hiding this comment

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

Given tests are passing, it makes me wonder how (or if) this is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually the authorization state (values a dev would stuff into a authorization URL. I'll rename for clarity.

Copy link

Choose a reason for hiding this comment

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

Ah. Damn you state and how general you are.

Comment on lines 350 to 354
connections = self.sso.list_connections()
all_connections = []

def test_delete_connection(self, setup_with_client_id, mock_raw_request_method):
mock_raw_request_method(
"delete",
"No Content",
204,
headers={"content-type": "text/plain; charset=utf-8"},
for connection in connections.auto_paging_iter():
all_connections.append(connection)
Copy link

Choose a reason for hiding this comment

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

We repeat something like this for every auto paging test. I wonder if it's worth having some helper. (Not in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, worthy of a helper for sure.

Comment on lines 464 to 465
request_params = request_kwargs["params"]
assert request_params["connection_type"] == "GenericSAML"
Copy link

Choose a reason for hiding this comment

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

For tests like this I wonder if we should explicitly assert all the params, like assert {"connection_type": "GenericSaml", "limit": 10} == request_kwargs["params"] or something similar. We know exactly how we're calling list_connections. it seems like we should know exactly how that translates into the HTTP request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a great idea. Updating.


params = {
"connection_type": connection_type.value if connection_type else None,
"connection_type": connection_type,
Copy link

Choose a reason for hiding this comment

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

We are not consistent about when we always add a key (even if it's None) or only add the key to the params if it's specified. I'm pretty sure I'm guilty of this as well. We should pick a style and go with it.

Copy link

Choose a reason for hiding this comment

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

... and I realized, if we do want to omit missing keys, I wonder if it's easier to come full circle and define our params as BaseModels.

Then we could just....

params = ConnectionParams(connection_type=connection_type)
# when it's time to use the params
Request(params=ConnectionParams.model_dump(exclude_unset=True))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Utilizing BaseModel could work. Also worth noting the HTTP client has a helper that removes all None values from query params before sending the request.

Copy link

Choose a reason for hiding this comment

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

Oh. In that case I guess we don't need to care.

workos/sso.py Outdated
return WorkOsListResource(
list_method=self.list_connections,
list_args=params,
**ListPage[Connection](**response).model_dump(exclude_unset=True),
Copy link

Choose a reason for hiding this comment

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

I explicitly added exclude_unset to the events endpoint because I didn't want to serialize the before key, which does not exist in the event list metadata.

Do you think we should extend this to all list endpoints? It might be moot, the API may not omit any fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, nope. Just a copy-pasta. Removing!

workos/sso.py Outdated
"domain": domain,
"organization_id": organization_id,
"limit": limit,
"limit": limit or DEFAULT_RESPONSE_LIMIT,
Copy link

Choose a reason for hiding this comment

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

Same here, didn't this get a value above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yep, now that I have Pylance working I can see this is unnecessary.

Comment on lines +4 to +39
ConnectionType = Literal[
"ADFSSAML",
"AdpOidc",
"AppleOAuth",
"Auth0SAML",
"AzureSAML",
"CasSAML",
"CloudflareSAML",
"ClassLinkSAML",
"CyberArkSAML",
"DuoSAML",
"GenericOIDC",
"GenericSAML",
"GitHubOAuth",
"GoogleOAuth",
"GoogleSAML",
"JumpCloudSAML",
"KeycloakSAML",
"LastPassSAML",
"LoginGovOidc",
"MagicLink",
"MicrosoftOAuth",
"MiniOrangeSAML",
"NetIqSAML",
"OktaSAML",
"OneLoginSAML",
"OracleSAML",
"PingFederateSAML",
"PingOneSAML",
"RipplingSAML",
"SalesforceSAML",
"ShibbolethGenericSAML",
"ShibbolethSAML",
"SimpleSamlPhpSAML",
"VMwareSAML",
]
Copy link

Choose a reason for hiding this comment

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

The API types also include pending and Auth0Migration types.

I think whoever owns SSO (is that us? LOL) could give us guidance around whether it's safe to omit these.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noticed that also. Let me ask.

@@ -172,3 +172,26 @@ async def request(
)
response = await self._client.request(**prepared_request_parameters)
return self._handle_response(response)


class FakeHTTPClient(SyncHTTPClient):
Copy link

Choose a reason for hiding this comment

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

🙈

@mattgd mattgd requested a review from tribble July 25, 2024 19:55
Copy link

@tribble tribble left a comment

Choose a reason for hiding this comment

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

Sheep it
sheep

@mattgd mattgd merged commit 2894241 into beta-5.0 Jul 25, 2024
5 checks passed
@mattgd mattgd deleted the feature/dsync-1735-add-types-to-connection-api-methods branch July 25, 2024 20:04
tribble pushed a commit that referenced this pull request Jul 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants