Skip to content

Commit

Permalink
Add identity_provider to SSO connections (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-stytch authored May 20, 2024
1 parent e8237bf commit 83be65f
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 18 deletions.
4 changes: 2 additions & 2 deletions stytch/b2b/api/discovery_organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def create(
This endpoint will also create an initial Member Session for the newly created Member.
The Member created by this endpoint will automatically be granted the `stytch_admin` Role. See the
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/stytch-defaults) for more details on this Role.
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/stytch-default) for more details on this Role.
If the new Organization is created with a `mfa_policy` of `REQUIRED_FOR_ALL`, the newly created Member will need to complete an MFA step to log in to the Organization.
The `intermediate_session_token` will not be consumed and instead will be returned in the response.
Expand Down Expand Up @@ -207,7 +207,7 @@ async def create_async(
This endpoint will also create an initial Member Session for the newly created Member.
The Member created by this endpoint will automatically be granted the `stytch_admin` Role. See the
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/stytch-defaults) for more details on this Role.
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/stytch-default) for more details on this Role.
If the new Organization is created with a `mfa_policy` of `REQUIRED_FOR_ALL`, the newly created Member will need to complete an MFA step to log in to the Organization.
The `intermediate_session_token` will not be consumed and instead will be returned in the response.
Expand Down
22 changes: 22 additions & 0 deletions stytch/b2b/api/organizations_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,17 @@ def delete_totp(
member_id: str,
method_options: Optional[DeleteTOTPRequestOptions] = None,
) -> DeleteTOTPResponse:
"""Delete a Member's MFA TOTP registration.
To mint a new registration for a Member, you must first call this endpoint to delete the existing registration.
Existing Member Sessions that include the TOTP authentication factor will not be revoked if the registration is deleted, and MFA will not be enforced until the Member logs in again.
/%}
Fields:
- organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value.
- member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
Expand All @@ -466,6 +477,17 @@ async def delete_totp_async(
member_id: str,
method_options: Optional[DeleteTOTPRequestOptions] = None,
) -> DeleteTOTPResponse:
"""Delete a Member's MFA TOTP registration.
To mint a new registration for a Member, you must first call this endpoint to delete the existing registration.
Existing Member Sessions that include the TOTP authentication factor will not be revoked if the registration is deleted, and MFA will not be enforced until the Member logs in again.
/%}
Fields:
- organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value.
- member_id: Globally unique UUID that identifies a specific Member. The `member_id` is critical to perform operations on a Member, so be sure to preserve this value.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
Expand Down
4 changes: 4 additions & 0 deletions stytch/b2b/api/organizations_members_oauth_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def google(
issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the
access token automatically.
Google One Tap does not return access tokens. If the member has only authenticated through Google One Tap and not through a regular Google OAuth flow, this endpoint will not return any tokens.
__Note:__ Google does not issue a refresh token on every login, and refresh tokens may expire if unused.
To force a refresh token to be issued, pass the `?provider_prompt=consent` query param into the
[Start Google OAuth flow](https://stytch.com/docs/b2b/api/oauth-google-start) endpoint.
Expand Down Expand Up @@ -68,6 +70,8 @@ async def google_async(
issued access token and ID token from the identity provider. If a refresh token has been issued, Stytch will refresh the
access token automatically.
Google One Tap does not return access tokens. If the member has only authenticated through Google One Tap and not through a regular Google OAuth flow, this endpoint will not return any tokens.
__Note:__ Google does not issue a refresh token on every login, and refresh tokens may expire if unused.
To force a refresh token to be issued, pass the `?provider_prompt=consent` query param into the
[Start Google OAuth flow](https://stytch.com/docs/b2b/api/oauth-google-start) endpoint.
Expand Down
8 changes: 6 additions & 2 deletions stytch/b2b/api/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def authenticate(
) -> AuthenticateResponse:
"""Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.
You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid.
You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
Expand Down Expand Up @@ -168,7 +168,7 @@ async def authenticate_async(
) -> AuthenticateResponse:
"""Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.
You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid.
You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid. See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://stytch.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
Expand Down Expand Up @@ -521,6 +521,8 @@ def get_jwks(
If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWKS to use for validation by inspecting the `kid` value.
See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
Fields:
- project_id: The `project_id` to get the JWKS for.
""" # noqa
Expand All @@ -547,6 +549,8 @@ async def get_jwks_async(
If you're using your own JWT validation library, many have built-in support for JWKS rotation, and you'll just need to supply this API endpoint. If not, your application should decide which JWKS to use for validation by inspecting the `kid` value.
See our [How to use Stytch Session JWTs](https://stytch.com/docs/b2b/guides/sessions/using-jwts) guide for more information.
Fields:
- project_id: The `project_id` to get the JWKS for.
""" # noqa
Expand Down
24 changes: 23 additions & 1 deletion stytch/b2b/api/sso_oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

from __future__ import annotations

from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Union

from stytch.b2b.models.sso_oidc import (
CreateConnectionRequestIdentityProvider,
CreateConnectionRequestOptions,
CreateConnectionResponse,
UpdateConnectionRequestIdentityProvider,
UpdateConnectionRequestOptions,
UpdateConnectionResponse,
)
Expand All @@ -30,13 +32,17 @@ def create_connection(
self,
organization_id: str,
display_name: Optional[str] = None,
identity_provider: Optional[
Union[CreateConnectionRequestIdentityProvider, str]
] = None,
method_options: Optional[CreateConnectionRequestOptions] = None,
) -> CreateConnectionResponse:
"""Create a new OIDC Connection. /%}
Fields:
- organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value.
- display_name: A human-readable display name for the connection.
- identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand All @@ -46,6 +52,8 @@ def create_connection(
}
if display_name is not None:
data["display_name"] = display_name
if identity_provider is not None:
data["identity_provider"] = identity_provider

url = self.api_base.url_for("/v1/b2b/sso/oidc/{organization_id}", data)
res = self.sync_client.post(url, data, headers)
Expand All @@ -55,13 +63,15 @@ async def create_connection_async(
self,
organization_id: str,
display_name: Optional[str] = None,
identity_provider: Optional[CreateConnectionRequestIdentityProvider] = None,
method_options: Optional[CreateConnectionRequestOptions] = None,
) -> CreateConnectionResponse:
"""Create a new OIDC Connection. /%}
Fields:
- organization_id: Globally unique UUID that identifies a specific Organization. The `organization_id` is critical to perform operations on an Organization, so be sure to preserve this value.
- display_name: A human-readable display name for the connection.
- identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand All @@ -71,6 +81,8 @@ async def create_connection_async(
}
if display_name is not None:
data["display_name"] = display_name
if identity_provider is not None:
data["identity_provider"] = identity_provider

url = self.api_base.url_for("/v1/b2b/sso/oidc/{organization_id}", data)
res = await self.async_client.post(url, data, headers)
Expand All @@ -88,6 +100,9 @@ def update_connection(
token_url: Optional[str] = None,
userinfo_url: Optional[str] = None,
jwks_url: Optional[str] = None,
identity_provider: Optional[
Union[UpdateConnectionRequestIdentityProvider, str]
] = None,
method_options: Optional[UpdateConnectionRequestOptions] = None,
) -> UpdateConnectionResponse:
"""Updates an existing OIDC connection.
Expand Down Expand Up @@ -121,6 +136,7 @@ def update_connection(
- token_url: The location of the URL that issues OAuth2.0 access tokens and OIDC ID tokens. This will be provided by the IdP.
- userinfo_url: The location of the IDP's [UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). This will be provided by the IdP.
- jwks_url: The location of the IdP's JSON Web Key Set, used to verify credentials issued by the IdP. This will be provided by the IdP.
- identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand All @@ -145,6 +161,8 @@ def update_connection(
data["userinfo_url"] = userinfo_url
if jwks_url is not None:
data["jwks_url"] = jwks_url
if identity_provider is not None:
data["identity_provider"] = identity_provider

url = self.api_base.url_for(
"/v1/b2b/sso/oidc/{organization_id}/connections/{connection_id}", data
Expand All @@ -164,6 +182,7 @@ async def update_connection_async(
token_url: Optional[str] = None,
userinfo_url: Optional[str] = None,
jwks_url: Optional[str] = None,
identity_provider: Optional[UpdateConnectionRequestIdentityProvider] = None,
method_options: Optional[UpdateConnectionRequestOptions] = None,
) -> UpdateConnectionResponse:
"""Updates an existing OIDC connection.
Expand Down Expand Up @@ -197,6 +216,7 @@ async def update_connection_async(
- token_url: The location of the URL that issues OAuth2.0 access tokens and OIDC ID tokens. This will be provided by the IdP.
- userinfo_url: The location of the IDP's [UserInfo Endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). This will be provided by the IdP.
- jwks_url: The location of the IdP's JSON Web Key Set, used to verify credentials issued by the IdP. This will be provided by the IdP.
- identity_provider: The identity provider of this connection. For OIDC, the accepted values are `generic`, `okta`, and `microsoft-entra`. For SAML, the accepted values are `generic`, `okta`, `microsoft-entra`, and `google-workspace`.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand All @@ -221,6 +241,8 @@ async def update_connection_async(
data["userinfo_url"] = userinfo_url
if jwks_url is not None:
data["jwks_url"] = jwks_url
if identity_provider is not None:
data["identity_provider"] = identity_provider

url = self.api_base.url_for(
"/v1/b2b/sso/oidc/{organization_id}/connections/{connection_id}", data
Expand Down
Loading

0 comments on commit 83be65f

Please sign in to comment.