Skip to content

Commit 13a021d

Browse files
ci-stytchStytch Codegen Botbgier-stytch
authored
Add User Roles for Consumer RBAC support (#290)
* Add User Roles for Consumer RBAC support * Fix missing vals --------- Co-authored-by: Stytch Codegen Bot <[email protected]> Co-authored-by: Brandon Gier <[email protected]>
1 parent c485ab1 commit 13a021d

File tree

9 files changed

+48
-9
lines changed

9 files changed

+48
-9
lines changed

stytch/b2b/api/sso_saml.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def update_connection(
126126
alternative_acs_url: Optional[str] = None,
127127
idp_initiated_auth_disabled: Optional[bool] = None,
128128
saml_encryption_private_key: Optional[str] = None,
129+
allow_gateway_callback: Optional[bool] = None,
129130
method_options: Optional[UpdateConnectionRequestOptions] = None,
130131
) -> UpdateConnectionResponse:
131132
"""Updates an existing SAML connection.
@@ -159,6 +160,7 @@ def update_connection(
159160
- alternative_acs_url: An alternative URL to use for the `AssertionConsumerServiceURL` in SP initiated SAML AuthNRequests. This value can be used when you wish to migrate an existing SAML integration to Stytch with zero downtime. Note that you will be responsible for proxying requests sent to the Alternative ACS URL to Stytch. Read our [SSO migration guide](https://stytch.com/docs/b2b/guides/migrations/additional-migration-considerations) for more info.
160161
- idp_initiated_auth_disabled: Determines whether IDP initiated auth is allowed for a given SAML connection. Defaults to false (IDP Initiated Auth is enabled).
161162
- saml_encryption_private_key: A PKCS1 format RSA private key used to decrypt encrypted SAML assertions. Only PKCS1 format (starting with "-----BEGIN RSA PRIVATE KEY-----") is supported.
163+
- allow_gateway_callback: (no documentation yet)
162164
""" # noqa
163165
headers: Dict[str, str] = {}
164166
if method_options is not None:
@@ -201,6 +203,8 @@ def update_connection(
201203
data["idp_initiated_auth_disabled"] = idp_initiated_auth_disabled
202204
if saml_encryption_private_key is not None:
203205
data["saml_encryption_private_key"] = saml_encryption_private_key
206+
if allow_gateway_callback is not None:
207+
data["allow_gateway_callback"] = allow_gateway_callback
204208

205209
url = self.api_base.url_for(
206210
"/v1/b2b/sso/saml/{organization_id}/connections/{connection_id}", data
@@ -230,6 +234,7 @@ async def update_connection_async(
230234
alternative_acs_url: Optional[str] = None,
231235
idp_initiated_auth_disabled: Optional[bool] = None,
232236
saml_encryption_private_key: Optional[str] = None,
237+
allow_gateway_callback: Optional[bool] = None,
233238
method_options: Optional[UpdateConnectionRequestOptions] = None,
234239
) -> UpdateConnectionResponse:
235240
"""Updates an existing SAML connection.
@@ -263,6 +268,7 @@ async def update_connection_async(
263268
- alternative_acs_url: An alternative URL to use for the `AssertionConsumerServiceURL` in SP initiated SAML AuthNRequests. This value can be used when you wish to migrate an existing SAML integration to Stytch with zero downtime. Note that you will be responsible for proxying requests sent to the Alternative ACS URL to Stytch. Read our [SSO migration guide](https://stytch.com/docs/b2b/guides/migrations/additional-migration-considerations) for more info.
264269
- idp_initiated_auth_disabled: Determines whether IDP initiated auth is allowed for a given SAML connection. Defaults to false (IDP Initiated Auth is enabled).
265270
- saml_encryption_private_key: A PKCS1 format RSA private key used to decrypt encrypted SAML assertions. Only PKCS1 format (starting with "-----BEGIN RSA PRIVATE KEY-----") is supported.
271+
- allow_gateway_callback: (no documentation yet)
266272
""" # noqa
267273
headers: Dict[str, str] = {}
268274
if method_options is not None:
@@ -305,6 +311,8 @@ async def update_connection_async(
305311
data["idp_initiated_auth_disabled"] = idp_initiated_auth_disabled
306312
if saml_encryption_private_key is not None:
307313
data["saml_encryption_private_key"] = saml_encryption_private_key
314+
if allow_gateway_callback is not None:
315+
data["allow_gateway_callback"] = allow_gateway_callback
308316

309317
url = self.api_base.url_for(
310318
"/v1/b2b/sso/saml/{organization_id}/connections/{connection_id}", data

stytch/b2b/models/sso.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class SAMLConnection(pydantic.BaseModel):
206206
nameid_format: str
207207
alternative_acs_url: str
208208
idp_initiated_auth_disabled: bool
209+
allow_gateway_callback: bool
209210
attribute_mapping: Optional[Dict[str, Any]] = None
210211

211212

stytch/consumer/api/users.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from __future__ import annotations
88

9-
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union
9+
from typing import Any, AsyncGenerator, Dict, Generator, List, Optional, Union
1010

1111
from stytch.consumer.models.attribute import Attributes
1212
from stytch.consumer.models.users import (
@@ -43,6 +43,7 @@ def __init__(
4343

4444
def create(
4545
self,
46+
roles: List[str],
4647
email: Optional[str] = None,
4748
name: Optional[Union[Name, Dict[str, Any]]] = None,
4849
attributes: Optional[Union[Attributes, Dict[str, Any]]] = None,
@@ -55,6 +56,8 @@ def create(
5556
"""Add a User to Stytch. A `user_id` is returned in the response that can then be used to perform other operations within Stytch. An `email` or a `phone_number` is required.
5657
5758
Fields:
59+
- roles: Roles to explicitly assign to this User.
60+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
5861
- email: The email address of the end user.
5962
- name: The name of the user. Each field in the name object is optional.
6063
- attributes: (no documentation yet)
@@ -69,7 +72,9 @@ def create(
6972
- external_id: An identifier that can be used in API calls wherever a user_id is expected. This is a string consisting of alphanumeric, `.`, `_`, `-`, or `|` characters with a maximum length of 128 characters.
7073
""" # noqa
7174
headers: Dict[str, str] = {}
72-
data: Dict[str, Any] = {}
75+
data: Dict[str, Any] = {
76+
"roles": roles,
77+
}
7378
if email is not None:
7479
data["email"] = email
7580
if name is not None:
@@ -95,6 +100,7 @@ def create(
95100

96101
async def create_async(
97102
self,
103+
roles: List[str],
98104
email: Optional[str] = None,
99105
name: Optional[Name] = None,
100106
attributes: Optional[Attributes] = None,
@@ -107,6 +113,8 @@ async def create_async(
107113
"""Add a User to Stytch. A `user_id` is returned in the response that can then be used to perform other operations within Stytch. An `email` or a `phone_number` is required.
108114
109115
Fields:
116+
- roles: Roles to explicitly assign to this User.
117+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
110118
- email: The email address of the end user.
111119
- name: The name of the user. Each field in the name object is optional.
112120
- attributes: (no documentation yet)
@@ -121,7 +129,9 @@ async def create_async(
121129
- external_id: An identifier that can be used in API calls wherever a user_id is expected. This is a string consisting of alphanumeric, `.`, `_`, `-`, or `|` characters with a maximum length of 128 characters.
122130
""" # noqa
123131
headers: Dict[str, str] = {}
124-
data: Dict[str, Any] = {}
132+
data: Dict[str, Any] = {
133+
"roles": roles,
134+
}
125135
if email is not None:
126136
data["email"] = email
127137
if name is not None:
@@ -263,6 +273,7 @@ def update(
263273
trusted_metadata: Optional[Dict[str, Any]] = None,
264274
untrusted_metadata: Optional[Dict[str, Any]] = None,
265275
external_id: Optional[str] = None,
276+
roles: Optional[List[str]] = None,
266277
) -> UpdateResponse:
267278
"""Update a User's attributes.
268279
@@ -275,6 +286,8 @@ def update(
275286
- trusted_metadata: The `trusted_metadata` field contains an arbitrary JSON object of application-specific data. See the [Metadata](https://stytch.com/docs/api/metadata) reference for complete field behavior details.
276287
- untrusted_metadata: The `untrusted_metadata` field contains an arbitrary JSON object of application-specific data. Untrusted metadata can be edited by end users directly via the SDK, and **cannot be used to store critical information.** See the [Metadata](https://stytch.com/docs/api/metadata) reference for complete field behavior details.
277288
- external_id: An identifier that can be used in API calls wherever a user_id is expected. This is a string consisting of alphanumeric, `.`, `_`, `-`, or `|` characters with a maximum length of 128 characters.
289+
- roles: Roles to explicitly assign to this User.
290+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
278291
""" # noqa
279292
headers: Dict[str, str] = {}
280293
data: Dict[str, Any] = {
@@ -292,6 +305,8 @@ def update(
292305
data["untrusted_metadata"] = untrusted_metadata
293306
if external_id is not None:
294307
data["external_id"] = external_id
308+
if roles is not None:
309+
data["roles"] = roles
295310

296311
url = self.api_base.url_for("/v1/users/{user_id}", data)
297312
res = self.sync_client.put(url, data, headers)
@@ -305,6 +320,7 @@ async def update_async(
305320
trusted_metadata: Optional[Dict[str, Any]] = None,
306321
untrusted_metadata: Optional[Dict[str, Any]] = None,
307322
external_id: Optional[str] = None,
323+
roles: Optional[List[str]] = None,
308324
) -> UpdateResponse:
309325
"""Update a User's attributes.
310326
@@ -317,6 +333,8 @@ async def update_async(
317333
- trusted_metadata: The `trusted_metadata` field contains an arbitrary JSON object of application-specific data. See the [Metadata](https://stytch.com/docs/api/metadata) reference for complete field behavior details.
318334
- untrusted_metadata: The `untrusted_metadata` field contains an arbitrary JSON object of application-specific data. Untrusted metadata can be edited by end users directly via the SDK, and **cannot be used to store critical information.** See the [Metadata](https://stytch.com/docs/api/metadata) reference for complete field behavior details.
319335
- external_id: An identifier that can be used in API calls wherever a user_id is expected. This is a string consisting of alphanumeric, `.`, `_`, `-`, or `|` characters with a maximum length of 128 characters.
336+
- roles: Roles to explicitly assign to this User.
337+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
320338
""" # noqa
321339
headers: Dict[str, str] = {}
322340
data: Dict[str, Any] = {
@@ -334,6 +352,8 @@ async def update_async(
334352
data["untrusted_metadata"] = untrusted_metadata
335353
if external_id is not None:
336354
data["external_id"] = external_id
355+
if roles is not None:
356+
data["roles"] = roles
337357

338358
url = self.api_base.url_for("/v1/users/{user_id}", data)
339359
res = await self.async_client.put(url, data, headers)

stytch/consumer/models/users.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ class User(pydantic.BaseModel):
201201
- crypto_wallets: An array contains a list of all crypto wallets for a given User in the Stytch API.
202202
- biometric_registrations: An array that contains a list of all biometric registrations for a given User in the Stytch API.
203203
- is_locked: (no documentation yet)
204+
- roles: Roles assigned to this User.
205+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
204206
- name: The name of the User. Each field in the `name` object is optional.
205207
- created_at: The timestamp of the User's creation. Values conform to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`.
206208
- password: The password object is returned for users with a password.
@@ -221,6 +223,7 @@ class User(pydantic.BaseModel):
221223
crypto_wallets: List[CryptoWallet]
222224
biometric_registrations: List[BiometricRegistration]
223225
is_locked: bool
226+
roles: List[str]
224227
name: Optional[Name] = None
225228
created_at: Optional[datetime.datetime] = None
226229
password: Optional[Password] = None
@@ -378,6 +381,8 @@ class GetResponse(ResponseBase):
378381
- crypto_wallets: An array contains a list of all crypto wallets for a given User in the Stytch API.
379382
- biometric_registrations: An array that contains a list of all biometric registrations for a given User in the Stytch API.
380383
- is_locked: (no documentation yet)
384+
- roles: Roles assigned to this User.
385+
See the [RBAC guide](https://stytch.com/docs/guides/rbac/role-assignment) for more information about role assignment.
381386
- name: The name of the User. Each field in the `name` object is optional.
382387
- created_at: The timestamp of the User's creation. Values conform to the RFC 3339 standard and are expressed in UTC, e.g. `2021-12-29T12:33:09Z`.
383388
- password: The password object is returned for users with a password.
@@ -398,6 +403,7 @@ class GetResponse(ResponseBase):
398403
crypto_wallets: List[CryptoWallet]
399404
biometric_registrations: List[BiometricRegistration]
400405
is_locked: bool
406+
roles: List[str]
401407
name: Optional[Name] = None
402408
created_at: Optional[datetime.datetime] = None
403409
password: Optional[Password] = None

stytch/core/api_base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ def __init__(self, base_url: str) -> None:
1111
def url_for(self, route: str, data: Dict[str, Any]) -> str:
1212
url = urllib.parse.urljoin(self.base_url, route)
1313
# URL-encode path parameters to handle special characters like + in email addresses
14-
encoded_data = {key: urllib.parse.quote(str(value), safe='') for key, value in data.items()}
14+
encoded_data = {
15+
key: urllib.parse.quote(str(value), safe="") for key, value in data.items()
16+
}
1517
return url.format(**encoded_data)
1618

1719
def route_with_sub_url(self, sub_url: str, route: Optional[str] = None) -> str:

stytch/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "13.20.1"
1+
__version__ = "13.21.0"

test/integration_base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ def _get_temporary_user(
104104
test_user = self.__get_temporary_test_user()
105105
if create:
106106
if via_magic_link:
107-
users_resp = self.b2c_client.users.create(email=test_user.email)
107+
users_resp = self.b2c_client.users.create(
108+
roles=[], email=test_user.email
109+
)
108110
user_id = users_resp.user_id
109111
else:
110112
pw_resp = self.b2c_client.passwords.create(
@@ -125,7 +127,7 @@ async def _get_temporary_user_async(
125127
if create:
126128
if via_magic_link:
127129
users_resp = await self.b2c_client.users.create_async(
128-
email=test_user.email
130+
roles=[], email=test_user.email
129131
)
130132
user_id = users_resp.user_id
131133
else:

test/test_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def test_users(self) -> None:
194194
api = self.b2c_client.users
195195

196196
with self._get_temporary_user(create=False) as user:
197-
create_resp = api.create(email=user.email)
197+
create_resp = api.create(roles=[], email=user.email)
198198
self.assertTrue(create_resp.is_success)
199199
self.assertTrue(api.search(limit=10).is_success)
200200
self.assertTrue(

test/test_integration_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ async def test_users_async(self) -> None:
267267
api = self.b2c_client.users
268268

269269
async with self._get_temporary_user_async(create=False) as user:
270-
create_resp = await api.create_async(email=user.email)
270+
create_resp = await api.create_async(roles=[], email=user.email)
271271
self.assertTrue(create_resp.is_success)
272272
self.assertTrue((await api.search_async(limit=10)).is_success)
273273
self.assertTrue(

0 commit comments

Comments
 (0)