Skip to content

Commit

Permalink
Add external connection endpoints (#221)
Browse files Browse the repository at this point in the history
* add external connection endpoints

* bump minor version
  • Loading branch information
etaylormcgregor-stytch authored Oct 10, 2024
1 parent f9487c9 commit 3e7155d
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 7 deletions.
8 changes: 8 additions & 0 deletions stytch/b2b/api/organizations_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,16 +604,20 @@ async def delete_password_async(
def dangerously_get(
self,
member_id: str,
include_deleted: Optional[bool] = None,
) -> GetResponse:
"""Get a Member by `member_id`. This endpoint does not require an `organization_id`, enabling you to get members across organizations. This is a dangerous operation. Incorrect use may open you up to indirect object reference (IDOR) attacks. We recommend using the [Get Member](https://stytch.com/docs/b2b/api/get-member) API instead.
Fields:
- 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.
- include_deleted: Whether to include deleted Members in the response. Defaults to false.
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"member_id": member_id,
}
if include_deleted is not None:
data["include_deleted"] = include_deleted

url = self.api_base.url_for(
"/v1/b2b/organizations/members/dangerously_get/{member_id}", data
Expand All @@ -624,16 +628,20 @@ def dangerously_get(
async def dangerously_get_async(
self,
member_id: str,
include_deleted: Optional[bool] = None,
) -> GetResponse:
"""Get a Member by `member_id`. This endpoint does not require an `organization_id`, enabling you to get members across organizations. This is a dangerous operation. Incorrect use may open you up to indirect object reference (IDOR) attacks. We recommend using the [Get Member](https://stytch.com/docs/b2b/api/get-member) API instead.
Fields:
- 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.
- include_deleted: Whether to include deleted Members in the response. Defaults to false.
""" # noqa
headers: Dict[str, str] = {}
data: Dict[str, Any] = {
"member_id": member_id,
}
if include_deleted is not None:
data["include_deleted"] = include_deleted

url = self.api_base.url_for(
"/v1/b2b/organizations/members/dangerously_get/{member_id}", data
Expand Down
10 changes: 8 additions & 2 deletions stytch/b2b/api/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from typing import Any, Dict, Optional, Union

from stytch.b2b.api.sso_external import External
from stytch.b2b.api.sso_oidc import OIDC
from stytch.b2b.api.sso_saml import SAML
from stytch.b2b.models.sso import (
Expand Down Expand Up @@ -39,6 +40,11 @@ def __init__(
sync_client=self.sync_client,
async_client=self.async_client,
)
self.external = External(
api_base=self.api_base,
sync_client=self.sync_client,
async_client=self.async_client,
)

def get_connections(
self,
Expand Down Expand Up @@ -92,7 +98,7 @@ def delete_connection(
Fields:
- organization_id: The organization ID that the SSO connection belongs to.
- connection_id: The ID of the SSO connection. Both SAML and OIDC connection IDs can be provided.
- connection_id: The ID of the SSO connection. SAML, OIDC, and External connection IDs can be provided.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand All @@ -118,7 +124,7 @@ async def delete_connection_async(
Fields:
- organization_id: The organization ID that the SSO connection belongs to.
- connection_id: The ID of the SSO connection. Both SAML and OIDC connection IDs can be provided.
- connection_id: The ID of the SSO connection. SAML, OIDC, and External connection IDs can be provided.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
Expand Down
239 changes: 239 additions & 0 deletions stytch/b2b/api/sso_external.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# !!!
# WARNING: This file is autogenerated
# Only modify code within MANUAL() sections
# or your changes may be overwritten later!
# !!!

from __future__ import annotations

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

from stytch.b2b.models.sso import (
ConnectionImplicitRoleAssignment,
GroupImplicitRoleAssignment,
SAMLConnectionImplicitRoleAssignment,
SAMLGroupImplicitRoleAssignment,
)
from stytch.b2b.models.sso_external import (
CreateConnectionRequestOptions,
CreateConnectionResponse,
UpdateConnectionRequestOptions,
UpdateConnectionResponse,
)
from stytch.core.api_base import ApiBase
from stytch.core.http.client import AsyncClient, SyncClient


class External:
def __init__(
self, api_base: ApiBase, sync_client: SyncClient, async_client: AsyncClient
) -> None:
self.api_base = api_base
self.sync_client = sync_client
self.async_client = async_client

def create_connection(
self,
organization_id: str,
external_organization_id: str,
external_connection_id: str,
display_name: Optional[str] = None,
connection_implicit_role_assignments: Optional[
List[Union[SAMLConnectionImplicitRoleAssignment, Dict[str, Any]]]
] = None,
group_implicit_role_assignments: Optional[
List[Union[SAMLGroupImplicitRoleAssignment, Dict[str, Any]]]
] = None,
method_options: Optional[CreateConnectionRequestOptions] = None,
) -> CreateConnectionResponse:
"""Create a new External SSO 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.
- external_organization_id: Globally unique UUID that identifies a different Organization within your Project.
- external_connection_id: Globally unique UUID that identifies a specific SSO connection configured for a different Organization in your Project.
- display_name: A human-readable display name for the connection.
- connection_implicit_role_assignments: (no documentation yet)
- group_implicit_role_assignments: (no documentation yet)
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"organization_id": organization_id,
"external_organization_id": external_organization_id,
"external_connection_id": external_connection_id,
}
if display_name is not None:
data["display_name"] = display_name
if connection_implicit_role_assignments is not None:
data["connection_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in connection_implicit_role_assignments
]
if group_implicit_role_assignments is not None:
data["group_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in group_implicit_role_assignments
]

url = self.api_base.url_for("/v1/b2b/sso/external/{organization_id}", data)
res = self.sync_client.post(url, data, headers)
return CreateConnectionResponse.from_json(res.response.status_code, res.json)

async def create_connection_async(
self,
organization_id: str,
external_organization_id: str,
external_connection_id: str,
display_name: Optional[str] = None,
connection_implicit_role_assignments: Optional[
List[SAMLConnectionImplicitRoleAssignment]
] = None,
group_implicit_role_assignments: Optional[
List[SAMLGroupImplicitRoleAssignment]
] = None,
method_options: Optional[CreateConnectionRequestOptions] = None,
) -> CreateConnectionResponse:
"""Create a new External SSO 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.
- external_organization_id: Globally unique UUID that identifies a different Organization within your Project.
- external_connection_id: Globally unique UUID that identifies a specific SSO connection configured for a different Organization in your Project.
- display_name: A human-readable display name for the connection.
- connection_implicit_role_assignments: (no documentation yet)
- group_implicit_role_assignments: (no documentation yet)
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"organization_id": organization_id,
"external_organization_id": external_organization_id,
"external_connection_id": external_connection_id,
}
if display_name is not None:
data["display_name"] = display_name
if connection_implicit_role_assignments is not None:
data["connection_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in connection_implicit_role_assignments
]
if group_implicit_role_assignments is not None:
data["group_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in group_implicit_role_assignments
]

url = self.api_base.url_for("/v1/b2b/sso/external/{organization_id}", data)
res = await self.async_client.post(url, data, headers)
return CreateConnectionResponse.from_json(res.response.status, res.json)

def update_connection(
self,
organization_id: str,
connection_id: str,
display_name: Optional[str] = None,
external_connection_implicit_role_assignments: Optional[
List[Union[ConnectionImplicitRoleAssignment, Dict[str, Any]]]
] = None,
external_group_implicit_role_assignments: Optional[
List[Union[GroupImplicitRoleAssignment, Dict[str, Any]]]
] = None,
method_options: Optional[UpdateConnectionRequestOptions] = None,
) -> UpdateConnectionResponse:
"""Updates an existing External SSO 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.
- connection_id: Globally unique UUID that identifies a specific External SSO Connection.
- display_name: A human-readable display name for the connection.
- external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection.
- external_group_implicit_role_assignments: Defines the names of the groups
that grant specific role assignments. For each group-Role pair, if a Member logs in with this external connection and
belongs to the specified group, they will be granted the associated Role. See the
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.
Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's
`attribute_mapping`. Make sure that the SAML connection IdP is configured to correctly send the group information. Implicit role assignments are not supported
for External connections if the underlying SSO connection is an OIDC connection.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"organization_id": organization_id,
"connection_id": connection_id,
}
if display_name is not None:
data["display_name"] = display_name
if external_connection_implicit_role_assignments is not None:
data["external_connection_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in external_connection_implicit_role_assignments
]
if external_group_implicit_role_assignments is not None:
data["external_group_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in external_group_implicit_role_assignments
]

url = self.api_base.url_for(
"/v1/b2b/sso/external/{organization_id}/connections/{connection_id}", data
)
res = self.sync_client.put(url, data, headers)
return UpdateConnectionResponse.from_json(res.response.status_code, res.json)

async def update_connection_async(
self,
organization_id: str,
connection_id: str,
display_name: Optional[str] = None,
external_connection_implicit_role_assignments: Optional[
List[ConnectionImplicitRoleAssignment]
] = None,
external_group_implicit_role_assignments: Optional[
List[GroupImplicitRoleAssignment]
] = None,
method_options: Optional[UpdateConnectionRequestOptions] = None,
) -> UpdateConnectionResponse:
"""Updates an existing External SSO 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.
- connection_id: Globally unique UUID that identifies a specific External SSO Connection.
- display_name: A human-readable display name for the connection.
- external_connection_implicit_role_assignments: All Members who log in with this External connection will implicitly receive the specified Roles. See the [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.Implicit role assignments are not supported for External connections if the underlying SSO connection is an OIDC connection.
- external_group_implicit_role_assignments: Defines the names of the groups
that grant specific role assignments. For each group-Role pair, if a Member logs in with this external connection and
belongs to the specified group, they will be granted the associated Role. See the
[RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.
Before adding any group implicit role assignments to an external connection, you must add a "groups" key to the underlying SAML connection's
`attribute_mapping`. Make sure that the SAML connection IdP is configured to correctly send the group information. Implicit role assignments are not supported
for External connections if the underlying SSO connection is an OIDC connection.
""" # noqa
headers: Dict[str, str] = {}
if method_options is not None:
headers = method_options.add_headers(headers)
data: Dict[str, Any] = {
"organization_id": organization_id,
"connection_id": connection_id,
}
if display_name is not None:
data["display_name"] = display_name
if external_connection_implicit_role_assignments is not None:
data["external_connection_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in external_connection_implicit_role_assignments
]
if external_group_implicit_role_assignments is not None:
data["external_group_implicit_role_assignments"] = [
item if isinstance(item, dict) else item.dict()
for item in external_group_implicit_role_assignments
]

url = self.api_base.url_for(
"/v1/b2b/sso/external/{organization_id}/connections/{connection_id}", data
)
res = await self.async_client.put(url, data, headers)
return UpdateConnectionResponse.from_json(res.response.status, res.json)
33 changes: 31 additions & 2 deletions stytch/b2b/models/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ class AuthenticateRequestLocale(str, enum.Enum):


class ConnectionImplicitRoleAssignment(pydantic.BaseModel):
"""
Fields:
- role_id: The unique identifier of the RBAC Role, provided by the developer and intended to be human-readable.
Reserved `role_id`s that are predefined by Stytch include:
* `stytch_member`
* `stytch_admin`
Check out the [guide on Stytch default Roles](https://stytch.com/docs/b2b/guides/rbac/stytch-default) for a more detailed explanation.
""" # noqa

role_id: str


Expand Down Expand Up @@ -62,6 +76,21 @@ def add_headers(self, headers: Dict[str, str]) -> Dict[str, str]:


class GroupImplicitRoleAssignment(pydantic.BaseModel):
"""
Fields:
- role_id: The unique identifier of the RBAC Role, provided by the developer and intended to be human-readable.
Reserved `role_id`s that are predefined by Stytch include:
* `stytch_member`
* `stytch_admin`
Check out the [guide on Stytch default Roles](https://stytch.com/docs/b2b/guides/rbac/stytch-default) for a more detailed explanation.
- group: The name of the group that grants the specified role assignment.
""" # noqa

role_id: str
group: str

Expand Down Expand Up @@ -128,7 +157,7 @@ class SAMLGroupImplicitRoleAssignment(pydantic.BaseModel):
Check out the [guide on Stytch default Roles](https://stytch.com/docs/b2b/guides/rbac/stytch-default) for a more detailed explanation.
- group: The name of the SAML group that grants the specified role assignment.
- group: The name of the group that grants the specified role assignment.
""" # noqa

role_id: str
Expand Down Expand Up @@ -207,7 +236,7 @@ class GetConnectionsResponse(ResponseBase):
Fields:
- saml_connections: The list of [SAML Connections](https://stytch.com/docs/b2b/api/saml-connection-object) owned by this organization.
- oidc_connections: The list of [OIDC Connections](https://stytch.com/docs/b2b/api/oidc-connection-object) owned by this organization.
- external_connections: (no documentation yet)
- external_connections: The list of [External Connections](https://stytch.com/docs/b2b/api/external-connection-object) owned by this organization.
""" # noqa

saml_connections: List[SAMLConnection]
Expand Down
Loading

0 comments on commit 3e7155d

Please sign in to comment.