scalekit-sdk-python 1.0.3__tar.gz → 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {scalekit_sdk_python-1.0.3/scalekit_sdk_python.egg-info → scalekit_sdk_python-1.0.4}/PKG-INFO +11 -11
  2. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/__init__.py +2 -1
  3. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/client.py +127 -9
  4. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/core.py +2 -0
  5. scalekit_sdk_python-1.0.4/scalekit/directory.py +243 -0
  6. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/organization.py +20 -1
  7. scalekit_sdk_python-1.0.4/scalekit/utils/directory.py +81 -0
  8. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2.py +27 -25
  9. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2.pyi +8 -4
  10. scalekit_sdk_python-1.0.4/scalekit/v1/commons/commons_pb2.py +62 -0
  11. scalekit_sdk_python-1.0.4/scalekit/v1/commons/commons_pb2.pyi +83 -0
  12. scalekit_sdk_python-1.0.4/scalekit/v1/connections/connections_pb2.py +450 -0
  13. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/connections/connections_pb2.pyi +70 -8
  14. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/connections/connections_pb2_grpc.py +33 -0
  15. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2.py +342 -0
  16. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2.pyi +414 -0
  17. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2_grpc.py +429 -0
  18. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2.py +65 -23
  19. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2.pyi +61 -1
  20. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2_grpc.py +165 -0
  21. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2.py +105 -0
  22. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2.pyi +279 -0
  23. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2_grpc.py +66 -0
  24. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2.py +69 -0
  25. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2.pyi +74 -0
  26. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2_grpc.py +132 -0
  27. scalekit_sdk_python-1.0.4/scalekit/v1/members/members_pb2.py +108 -0
  28. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/members/members_pb2.pyi +11 -4
  29. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2.py +48 -0
  30. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2.pyi +37 -0
  31. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2_grpc.py +133 -0
  32. scalekit_sdk_python-1.0.4/scalekit/v1/organizations/__init__.py +0 -0
  33. scalekit_sdk_python-1.0.4/scalekit/v1/organizations/organizations_pb2.py +217 -0
  34. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/organizations/organizations_pb2.pyi +41 -8
  35. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/organizations/organizations_pb2_grpc.py +33 -0
  36. scalekit_sdk_python-1.0.4/scalekit/v1/roles/__init__.py +0 -0
  37. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2.py +109 -0
  38. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2.pyi +116 -0
  39. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2_grpc.py +199 -0
  40. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/__init__.py +0 -0
  41. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2.py +95 -0
  42. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2.pyi +111 -0
  43. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2_grpc.py +298 -0
  44. scalekit_sdk_python-1.0.4/scalekit/v1/users/__init__.py +0 -0
  45. scalekit_sdk_python-1.0.4/scalekit/v1/users/users_pb2.py +104 -0
  46. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/users/users_pb2.pyi +49 -18
  47. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/users/users_pb2_grpc.py +33 -0
  48. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/__init__.py +0 -0
  49. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2.py +43 -0
  50. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2.pyi +31 -0
  51. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2_grpc.py +100 -0
  52. scalekit_sdk_python-1.0.4/scalekit/v1/workspaces/__init__.py +0 -0
  53. scalekit_sdk_python-1.0.4/scalekit/v1/workspaces/workspaces_pb2.py +130 -0
  54. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/workspaces/workspaces_pb2.pyi +74 -1
  55. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/workspaces/workspaces_pb2_grpc.py +133 -0
  56. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4/scalekit_sdk_python.egg-info}/PKG-INFO +11 -11
  57. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/SOURCES.txt +27 -4
  58. scalekit_sdk_python-1.0.4/scalekit_sdk_python.egg-info/requires.txt +10 -0
  59. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/setup.py +11 -11
  60. scalekit_sdk_python-1.0.3/scalekit/v1/commons/commons_pb2.py +0 -36
  61. scalekit_sdk_python-1.0.3/scalekit/v1/commons/commons_pb2.pyi +0 -30
  62. scalekit_sdk_python-1.0.3/scalekit/v1/connections/connections_pb2.py +0 -409
  63. scalekit_sdk_python-1.0.3/scalekit/v1/events/events_pb2.py +0 -39
  64. scalekit_sdk_python-1.0.3/scalekit/v1/events/events_pb2.pyi +0 -62
  65. scalekit_sdk_python-1.0.3/scalekit/v1/events/events_pb2_grpc.py +0 -4
  66. scalekit_sdk_python-1.0.3/scalekit/v1/members/members_pb2.py +0 -105
  67. scalekit_sdk_python-1.0.3/scalekit/v1/organizations/organizations_pb2.py +0 -188
  68. scalekit_sdk_python-1.0.3/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2.py +0 -83
  69. scalekit_sdk_python-1.0.3/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2.pyi +0 -99
  70. scalekit_sdk_python-1.0.3/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2_grpc.py +0 -174
  71. scalekit_sdk_python-1.0.3/scalekit/v1/users/users_pb2.py +0 -97
  72. scalekit_sdk_python-1.0.3/scalekit/v1/workspaces/workspaces_pb2.py +0 -90
  73. scalekit_sdk_python-1.0.3/scalekit_sdk_python.egg-info/requires.txt +0 -10
  74. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/LICENSE +0 -0
  75. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/README.md +0 -0
  76. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/__init__.py +0 -0
  77. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/__init__.py +0 -0
  78. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2.py +0 -0
  79. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2.pyi +0 -0
  80. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2_grpc.py +0 -0
  81. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/priv/__init__.py +0 -0
  82. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2.py +0 -0
  83. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2.pyi +0 -0
  84. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2_grpc.py +0 -0
  85. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2.py +0 -0
  86. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2.pyi +0 -0
  87. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2_grpc.py +0 -0
  88. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/common/__init__.py +0 -0
  89. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/common/scalekit.py +0 -0
  90. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/common/user.py +0 -0
  91. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/connection.py +0 -0
  92. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/constants/__init__.py +0 -0
  93. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/constants/user.py +0 -0
  94. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/domain.py +0 -0
  95. {scalekit_sdk_python-1.0.3/scalekit/v1 → scalekit_sdk_python-1.0.4/scalekit/utils}/__init__.py +0 -0
  96. {scalekit_sdk_python-1.0.3/scalekit/v1/clients → scalekit_sdk_python-1.0.4/scalekit/v1}/__init__.py +0 -0
  97. {scalekit_sdk_python-1.0.3/scalekit/v1/commons → scalekit_sdk_python-1.0.4/scalekit/v1/clients}/__init__.py +0 -0
  98. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2_grpc.py +0 -0
  99. {scalekit_sdk_python-1.0.3/scalekit/v1/connections → scalekit_sdk_python-1.0.4/scalekit/v1/commons}/__init__.py +0 -0
  100. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/commons/commons_pb2_grpc.py +0 -0
  101. {scalekit_sdk_python-1.0.3/scalekit/v1/domains → scalekit_sdk_python-1.0.4/scalekit/v1/connections}/__init__.py +0 -0
  102. {scalekit_sdk_python-1.0.3/scalekit/v1/environments → scalekit_sdk_python-1.0.4/scalekit/v1/directories}/__init__.py +0 -0
  103. {scalekit_sdk_python-1.0.3/scalekit/v1/errdetails → scalekit_sdk_python-1.0.4/scalekit/v1/domains}/__init__.py +0 -0
  104. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2.py +0 -0
  105. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2.pyi +0 -0
  106. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2_grpc.py +0 -0
  107. {scalekit_sdk_python-1.0.3/scalekit/v1/events → scalekit_sdk_python-1.0.4/scalekit/v1/environments}/__init__.py +0 -0
  108. {scalekit_sdk_python-1.0.3/scalekit/v1/members → scalekit_sdk_python-1.0.4/scalekit/v1/errdetails}/__init__.py +0 -0
  109. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2.py +0 -0
  110. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2.pyi +0 -0
  111. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2_grpc.py +0 -0
  112. {scalekit_sdk_python-1.0.3/scalekit/v1/options → scalekit_sdk_python-1.0.4/scalekit/v1/events}/__init__.py +0 -0
  113. {scalekit_sdk_python-1.0.3/scalekit/v1/organizations → scalekit_sdk_python-1.0.4/scalekit/v1/login_box}/__init__.py +0 -0
  114. {scalekit_sdk_python-1.0.3/scalekit/v1/user_profile_attributes → scalekit_sdk_python-1.0.4/scalekit/v1/members}/__init__.py +0 -0
  115. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/members/members_pb2_grpc.py +0 -0
  116. {scalekit_sdk_python-1.0.3/scalekit/v1/users → scalekit_sdk_python-1.0.4/scalekit/v1/migrations}/__init__.py +0 -0
  117. {scalekit_sdk_python-1.0.3/scalekit/v1/workspaces → scalekit_sdk_python-1.0.4/scalekit/v1/options}/__init__.py +0 -0
  118. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2.py +0 -0
  119. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2.pyi +0 -0
  120. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2_grpc.py +0 -0
  121. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/dependency_links.txt +0 -0
  122. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/top_level.txt +0 -0
  123. {scalekit_sdk_python-1.0.3 → scalekit_sdk_python-1.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scalekit-sdk-python
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: Scalekit official Python SDK
5
5
  Home-page: https://github.com/scalekit-inc/scalekit-sdk-python
6
6
  Author: Team Scalekit
@@ -11,16 +11,16 @@ Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Operating System :: OS Independent
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: grpcio~=1.64.1
15
- Requires-Dist: protobuf~=5.27.0
16
- Requires-Dist: google~=3.0.0
17
- Requires-Dist: requests~=2.32.3
18
- Requires-Dist: PyJWT~=2.8.0
19
- Requires-Dist: cryptography~=42.0.8
20
- Requires-Dist: setuptools~=70.3.0
21
- Requires-Dist: grpcio-status~=1.64.0
22
- Requires-Dist: protoc-gen-openapiv2~=0.0.1
23
- Requires-Dist: googleapis-common-protos~=1.56.1
14
+ Requires-Dist: grpcio>=1.64.1
15
+ Requires-Dist: protobuf>=5.27.0
16
+ Requires-Dist: google>=3.0.0
17
+ Requires-Dist: requests>=2.32.3
18
+ Requires-Dist: PyJWT>=2.8.0
19
+ Requires-Dist: cryptography>=43.0.3
20
+ Requires-Dist: setuptools>=70.3.0
21
+ Requires-Dist: grpcio-status>=1.64.0
22
+ Requires-Dist: protoc-gen-openapiv2>=0.0.1
23
+ Requires-Dist: googleapis-common-protos>=1.56.1
24
24
 
25
25
  <p align="left">
26
26
  <a href="https://scalekit.com" target="_blank" rel="noopener noreferrer">
@@ -1,4 +1,5 @@
1
+
1
2
  from scalekit.client import ScalekitClient
2
- from scalekit.common.scalekit import AuthorizationUrlOptions, CodeAuthenticationOptions
3
+ from scalekit.common.scalekit import CodeAuthenticationOptions, AuthorizationUrlOptions
3
4
 
4
5
  __all__ = ['ScalekitClient', 'AuthorizationUrlOptions', 'CodeAuthenticationOptions']
@@ -1,12 +1,19 @@
1
- from typing import Any, Dict, Optional
1
+
2
2
  import json
3
+ from math import floor
4
+ from typing import Any, Optional, Dict
3
5
  from urllib.parse import urlencode
4
6
 
5
7
  import jwt
8
+ import hmac
9
+ import hashlib
10
+ import base64
11
+ from datetime import datetime, timedelta, timezone
6
12
  from scalekit.core import CoreClient
7
13
  from scalekit.domain import DomainClient
8
14
  from scalekit.connection import ConnectionClient
9
15
  from scalekit.organization import OrganizationClient
16
+ from scalekit.directory import DirectoryClient
10
17
  from scalekit.common.scalekit import (
11
18
  AuthorizationUrlOptions,
12
19
  CodeAuthenticationOptions,
@@ -16,10 +23,16 @@ from scalekit.common.scalekit import (
16
23
  from scalekit.constants.user import id_token_claim_to_user_map
17
24
 
18
25
  AUTHORIZE_ENDPOINT = "oauth/authorize"
26
+ webhook_tolerance_in_seconds = timedelta(minutes=5)
27
+ webhook_signature_version = "v1"
28
+
29
+
30
+ class WebhookVerificationError(Exception):
31
+ pass
19
32
 
20
33
 
21
34
  class ScalekitClient:
22
- """ """
35
+ """ Class definition for scalekit client """
23
36
 
24
37
  def __init__(self, env_url: str, client_id: str, client_secret: str):
25
38
  """
@@ -31,7 +44,8 @@ class ScalekitClient:
31
44
  :type : ``` str ```
32
45
  :param client_secret : Client Secret
33
46
  :type : ``` str ```
34
- :returns
47
+
48
+ :returns:
35
49
  None
36
50
  """
37
51
  try:
@@ -41,6 +55,7 @@ class ScalekitClient:
41
55
  self.domain = DomainClient(self.core_client)
42
56
  self.connection = ConnectionClient(self.core_client)
43
57
  self.organization = OrganizationClient(self.core_client)
58
+ self.directory = DirectoryClient(self.core_client)
44
59
  except Exception as exp:
45
60
  raise exp
46
61
 
@@ -52,9 +67,10 @@ class ScalekitClient:
52
67
 
53
68
  :param redirect_uri : Redirect URI for SAML SSO
54
69
  :type : ``` str ```
55
- :param options : ``` Auth URL options object```
70
+ :param options : Auth URL options object
56
71
  :type : ``` obj ```
57
- :returns
72
+
73
+ :returns:
58
74
  Authorization URL
59
75
  """
60
76
  try:
@@ -92,8 +108,9 @@ class ScalekitClient:
92
108
  :type : ``` str ```
93
109
  :param redirect_uri : Redirect URI
94
110
  :type : ``` str ```
95
- :param options : ``` CodeAuthenticationOptions Object ```
111
+ :param options : CodeAuthenticationOptions Object
96
112
  :type : ``` obj ```
113
+
97
114
  :returns:
98
115
  dict with user, id token & access token
99
116
  """
@@ -131,7 +148,8 @@ class ScalekitClient:
131
148
 
132
149
  :param token : access token
133
150
  :type : ``` str ```
134
- :returns
151
+
152
+ :returns:
135
153
  bool
136
154
  """
137
155
  try:
@@ -146,7 +164,8 @@ class ScalekitClient:
146
164
 
147
165
  :param idp_initiated_login_token : IDP initiated login token
148
166
  :type : ``` str ```
149
- :returns
167
+
168
+ :returns:
150
169
  ``` IdpInitiatedLoginClaims ```
151
170
  """
152
171
  try:
@@ -163,7 +182,8 @@ class ScalekitClient:
163
182
 
164
183
  :param token : token
165
184
  :type : ``` str ```
166
- :returns
185
+
186
+ :returns:
167
187
  payload
168
188
  """
169
189
  self.core_client.get_jwks()
@@ -171,3 +191,101 @@ class ScalekitClient:
171
191
  key = self.core_client.keys[kid]
172
192
 
173
193
  return jwt.decode(token, key=key, algorithms="RS256", options=options)
194
+
195
+ def verify_webhook_payload(self, secret: str, headers: Dict[str, str], payload: [str | bytes]) -> bool:
196
+ """
197
+ Method to verify webhook payload
198
+
199
+ :param secret : Secret for webhook verification
200
+ :type : ``` str ```
201
+ :param headers : Webhook request headers
202
+ :type : ``` dict[str, str] ```
203
+ :param payload : Webhook payload in str or bytes
204
+ :type : ``` str | bytes ```
205
+
206
+ :returns:
207
+ bool
208
+ """
209
+ payload = payload if isinstance(payload, str) else payload.decode()
210
+ webhook_id = headers.get("webhook-id")
211
+ webhook_timestamp = headers.get("webhook-timestamp")
212
+ webhook_signature = headers.get("webhook-signature")
213
+
214
+ if not all([webhook_id, webhook_timestamp, webhook_signature]):
215
+ raise WebhookVerificationError("Missing required headers")
216
+
217
+ secret_parts = secret.split("_")
218
+ if len(secret_parts) < 2:
219
+ raise WebhookVerificationError("Invalid secret")
220
+
221
+ try:
222
+ secret_bytes = base64.b64decode(secret_parts[1])
223
+ except Exception as exp:
224
+ raise exp
225
+
226
+ try:
227
+ timestamp = self.__verify_timestamp(webhook_timestamp)
228
+ except Exception as exp:
229
+ raise exp
230
+
231
+ timestamp_str = str(floor(timestamp.replace(tzinfo=timezone.utc).timestamp()))
232
+ data = f"{webhook_id}.{timestamp_str}.{payload}"
233
+ computed_signature = base64.b64decode(self.__compute_signature(secret_bytes, data).split(',')[1])
234
+
235
+ received_signatures = webhook_signature.split(" ")
236
+ for versioned_signature in received_signatures:
237
+ signature_parts = versioned_signature.split(",")
238
+ if len(signature_parts) < 2:
239
+ continue
240
+
241
+ version = signature_parts[0]
242
+ signature = base64.b64decode(signature_parts[1])
243
+
244
+ if version != webhook_signature_version:
245
+ continue
246
+
247
+ if hmac.compare_digest(signature, computed_signature):
248
+ return True
249
+
250
+ raise WebhookVerificationError("Invalid signature")
251
+
252
+ @staticmethod
253
+ def __verify_timestamp(timestamp_str: str):
254
+ """
255
+ Method to verify time stamp
256
+
257
+ :param timestamp_str : Timestamp for verification
258
+ :type : ``` str ```
259
+
260
+ :returns:
261
+ None
262
+ """
263
+ now = datetime.now(tz=timezone.utc)
264
+ try:
265
+ timestamp = datetime.fromtimestamp(float(timestamp_str), tz=timezone.utc)
266
+ except Exception:
267
+ raise WebhookVerificationError("Invalid Signature Headers")
268
+
269
+ if timestamp < (now - webhook_tolerance_in_seconds):
270
+ raise Exception("Message timestamp too old")
271
+
272
+ if timestamp > (now + webhook_tolerance_in_seconds):
273
+ raise Exception("Message timestamp too new")
274
+
275
+ return timestamp
276
+
277
+ @staticmethod
278
+ def __compute_signature(secret: bytes, data: str) -> str:
279
+ """
280
+ Method to compute signature
281
+
282
+ :param secret : secret for signature
283
+ :type : ``` bytes ```
284
+ :param data : data for signature
285
+ :type : ``` str ```
286
+
287
+ :returns:
288
+ None
289
+ """
290
+ signature = hmac.new(secret, data.encode(), hashlib.sha256).digest()
291
+ return f"v1, {base64.b64encode(signature).decode('utf-8')}"
@@ -83,6 +83,8 @@ class CoreClient:
83
83
  }
84
84
 
85
85
  response = self.authenticate(data=json.dumps(params))
86
+ if response.status_code != 200:
87
+ raise Exception(response.content)
86
88
  response = json.loads(response.content)
87
89
  self.access_token = response["access_token"]
88
90
 
@@ -0,0 +1,243 @@
1
+ from typing import Optional, Any
2
+ from google.protobuf.json_format import MessageToJson
3
+
4
+ from scalekit.core import CoreClient
5
+ from scalekit.utils.directory import DirUser, ListDirUsersResponse, DirGroup, ListDirGroupsResponse
6
+
7
+ from scalekit.v1.directories.directories_pb2 import *
8
+ from scalekit.v1.directories.directories_pb2_grpc import DirectoryServiceStub
9
+
10
+
11
+ class DirectoryClient:
12
+ """ Class definition for Directory Client """
13
+
14
+ def __init__(self, core_client: CoreClient):
15
+ """
16
+ Initializer for Directory Client
17
+
18
+ :param core_client : CoreClient Object
19
+ :type : ``` obj ```
20
+
21
+ :returns
22
+ None
23
+ """
24
+ self.core_client = core_client
25
+ self.directory_service = DirectoryServiceStub(
26
+ self.core_client.grpc_secure_channel
27
+ )
28
+
29
+ def get_directory(
30
+ self,
31
+ organization_id: str,
32
+ directory_id: str,
33
+ ) -> GetDirectoryResponse:
34
+ """
35
+ Method to get directory based on given organization and directory id
36
+
37
+ :param organization_id : Organization id
38
+ :type : ``` str ```
39
+ :param directory_id : directory id
40
+ :type : ``` str ```
41
+
42
+ :returns:
43
+ Get Directory Response
44
+ """
45
+ return self.core_client.grpc_exec(
46
+ self.directory_service.GetDirectory.with_call,
47
+ GetDirectoryRequest(
48
+ id=directory_id,
49
+ organization_id=organization_id,
50
+ ),
51
+ )
52
+
53
+ def list_directories(self, organization_id) -> ListDirectoriesResponse:
54
+ """
55
+ Method to list directories for given organization id
56
+
57
+ :param organization_id : org id to fetch directory list
58
+ :type : ``` str ```
59
+
60
+ :returns:
61
+ list of directories
62
+ """
63
+ return self.core_client.grpc_exec(
64
+ self.directory_service.ListDirectories.with_call,
65
+ ListDirectoriesRequest(organization_id=organization_id),
66
+ )
67
+
68
+ def list_directory_users(
69
+ self,
70
+ organization_id: str,
71
+ directory_id: str,
72
+ page_size: Optional[int] = None,
73
+ page_token: Optional[str] = None,
74
+ include_detail: Optional[bool] = None,
75
+ updated_after: Optional[str] = None
76
+ ) -> tuple[ListDirUsersResponse, Any]:
77
+ """
78
+ Method to fetch list of directory users based on given organization and directory id
79
+
80
+ :param organization_id : Organization id
81
+ :type : ``` str ```
82
+ :param directory_id : directory id
83
+ :type : ``` str ```
84
+ :param page_size : page size for org list fetch
85
+ :type : ``` int ```
86
+ :param page_token : page token for org list fetch
87
+ :type : ``` str ```
88
+ :param include_detail : param to include detailed data
89
+ :type : ``` bool ```
90
+ :param updated_after : param to get updated after detail
91
+ :type : ``` str ```
92
+
93
+ :returns:
94
+ list of directory users
95
+ """
96
+ response = self.core_client.grpc_exec(
97
+ self.directory_service.ListDirectoryUsers.with_call,
98
+ ListDirectoryUsersRequest(
99
+ organization_id=organization_id,
100
+ directory_id=directory_id,
101
+ page_size=page_size,
102
+ page_token=page_token,
103
+ include_detail=include_detail,
104
+ updated_after=updated_after
105
+ ),
106
+ )
107
+
108
+ user_response = (ListDirUsersResponse(), response[1])
109
+ for user in response[0].users:
110
+ dir_user = DirUser()
111
+ dir_user.id = user.id
112
+ dir_user.email = user.email
113
+ dir_user.preferred_username = user.preferred_username
114
+ dir_user.given_name = user.given_name
115
+ dir_user.family_name = user.family_name
116
+ dir_user.updated_at = user.updated_at
117
+ dir_user.emails = user.emails
118
+ dir_user.groups = user.groups
119
+ dir_user.user_detail = MessageToJson(user.user_detail)
120
+
121
+ if not hasattr(user_response[0], 'users'):
122
+ user_response[0].users = [dir_user]
123
+ else:
124
+ user_response[0].users.append(dir_user)
125
+ user_response[0].total_size = response[0].total_size
126
+ user_response[0].next_page_token = response[0].next_page_token
127
+ user_response[0].prev_page_token = response[0].prev_page_token
128
+
129
+ return user_response
130
+
131
+ def list_directory_groups(
132
+ self,
133
+ organization_id: str,
134
+ directory_id: str,
135
+ page_size: Optional[int] = None,
136
+ page_token: Optional[str] = None,
137
+ include_detail: Optional[bool] = None,
138
+ updated_after: Optional[str] = None
139
+ ) -> tuple[ListDirGroupsResponse, Any]:
140
+ """
141
+ Method to fetch list of directory groups based on given organization and directory id
142
+
143
+ :param organization_id : Organization id
144
+ :type : ``` str ```
145
+ :param directory_id : directory id
146
+ :type : ``` str ```
147
+ :param page_size : page size for org list fetch
148
+ :type : ``` int ```
149
+ :param page_token : page token for org list fetch
150
+ :type : ``` str ```
151
+ :param include_detail : param to include detailed data
152
+ :type : ``` bool ```
153
+ :param updated_after : param to get updated after detail
154
+ :type : ``` str ```
155
+
156
+ :returns:
157
+ list of directory users
158
+ """
159
+ response = self.core_client.grpc_exec(
160
+ self.directory_service.ListDirectoryGroups.with_call,
161
+ ListDirectoryGroupsRequest(
162
+ organization_id=organization_id,
163
+ directory_id=directory_id,
164
+ page_size=page_size,
165
+ page_token=page_token,
166
+ include_detail=include_detail,
167
+ updated_after=updated_after
168
+ ),
169
+ )
170
+
171
+ group_response = (ListDirGroupsResponse(), response[1])
172
+ for group in response[0].groups:
173
+ dir_group = DirGroup()
174
+ dir_group.id = group.id
175
+ dir_group.display_name = group.display_name
176
+ dir_group.total_users = group.total_users
177
+ dir_group.updated_at = group.updated_at
178
+ dir_group.group_detail = MessageToJson(group.group_detail)
179
+
180
+ if not hasattr(group_response, 'users'):
181
+ group_response[0].groups = [dir_group]
182
+ else:
183
+ group_response[0].groups.append(dir_group)
184
+
185
+ group_response[0].total_size = response[0].total_size
186
+ group_response[0].next_page_token = response[0].next_page_token
187
+ group_response[0].prev_page_token = response[0].prev_page_token
188
+
189
+ return group_response
190
+
191
+ def enable_directory(self, organization_id: str, directory_id: str) -> ToggleDirectoryResponse:
192
+ """
193
+ Method to enable directory based on given organization and directory id
194
+
195
+ :param organization_id : Organization id
196
+ :type : ``` str ```
197
+ :param directory_id : directory id
198
+ :type : ``` str ```
199
+
200
+ :returns:
201
+ Toggle Directory Response
202
+ """
203
+ return self.core_client.grpc_exec(
204
+ self.directory_service.EnableDirectory.with_call,
205
+ ToggleDirectoryRequest(organization_id=organization_id, id=directory_id),
206
+ )
207
+
208
+ def disable_directory(self, organization_id: str, directory_id: str) -> ToggleDirectoryResponse:
209
+ """
210
+ Method to disable directory based on given organization and directory id
211
+
212
+ :param organization_id : Organization id
213
+ :type : ``` str ```
214
+ :param directory_id : directory id
215
+ :type : ``` str ```
216
+
217
+ :returns:
218
+ Toggle Directory Response
219
+ """
220
+ return self.core_client.grpc_exec(
221
+ self.directory_service.DisableDirectory.with_call,
222
+ ToggleDirectoryRequest(organization_id=organization_id, id=directory_id),
223
+ )
224
+
225
+ def get_primary_directory_by_organization_id(self, organization_id: str):
226
+ """
227
+ Method to get primary directory based on given organization id
228
+
229
+ :param organization_id : Organization id
230
+ :type : ``` str ```
231
+
232
+ :returns:
233
+ Primary directory
234
+ """
235
+ response = self.core_client.grpc_exec(
236
+ self.directory_service.ListDirectories.with_call,
237
+ ListDirectoriesRequest(organization_id=organization_id),
238
+ )
239
+
240
+ if response[0].directories:
241
+ return response[0].directories[0]
242
+ else:
243
+ raise Exception("Directory does not exist for given Organization Id.")
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from typing import Optional, List, Dict
2
2
 
3
3
  from scalekit.core import CoreClient
4
4
  from scalekit.v1.organizations.organizations_pb2 import *
@@ -172,6 +172,7 @@ class OrganizationClient:
172
172
 
173
173
  :param organization_id : Organization id to delete portal link for
174
174
  :type : ``` str ```
175
+
175
176
  :returns:
176
177
  None
177
178
  """
@@ -179,3 +180,21 @@ class OrganizationClient:
179
180
  self.organization_service.DeletePortalLink.with_call,
180
181
  DeletePortalLinkRequest(id=organization_id),
181
182
  )
183
+
184
+ def update_organization_settings(self, organization_id: str, settings: List[Dict[str, bool]]):
185
+ """
186
+ Method to update organization settings
187
+
188
+ :param organization_id : Organization id for org update
189
+ :type : ``` str ```
190
+ :param settings : Organization settings
191
+ :type : ``` list[dict[str, bool]] ```
192
+
193
+ :returns:
194
+ None
195
+ """
196
+ self.core_client.grpc_exec(
197
+ self.organization_service.UpdateOrganizationSettings.with_call,
198
+ UpdateOrganizationSettingsRequest(
199
+ id=organization_id, settings={'features': settings})
200
+ )
@@ -0,0 +1,81 @@
1
+
2
+ from google.protobuf import message as _message
3
+ from google.protobuf import timestamp_pb2 as _timestamp_pb2
4
+ from google.protobuf.internal import containers as _containers
5
+ from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, \
6
+ Union as _Union
7
+
8
+ from scalekit.v1.directories.directories_pb2 import DirectoryGroup
9
+
10
+
11
+ class DirUser(_message.Message):
12
+ """ """
13
+ __slots__ = ("id", "email", "preferred_username", "given_name", "family_name", "updated_at", "emails", "groups", "user_detail")
14
+ ID_FIELD_NUMBER: _ClassVar[int]
15
+ EMAIL_FIELD_NUMBER: _ClassVar[int]
16
+ PREFERRED_USERNAME_FIELD_NUMBER: _ClassVar[int]
17
+ GIVEN_NAME_FIELD_NUMBER: _ClassVar[int]
18
+ FAMILY_NAME_FIELD_NUMBER: _ClassVar[int]
19
+ UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
20
+ EMAILS_FIELD_NUMBER: _ClassVar[int]
21
+ GROUPS_FIELD_NUMBER: _ClassVar[int]
22
+ USER_DETAIL_FIELD_NUMBER: _ClassVar[int]
23
+ id: str | None
24
+ email: str | None
25
+ preferred_username: str | None
26
+ given_name: str | None
27
+ family_name: str | None
28
+ updated_at: _timestamp_pb2.Timestamp | None
29
+ emails: _containers.RepeatedScalarFieldContainer[str] | None
30
+ groups: _containers.RepeatedCompositeFieldContainer[DirectoryGroup] | None
31
+ user_detail: str | None
32
+
33
+ def __init__(self): ...
34
+
35
+
36
+ class DirGroup(_message.Message):
37
+ """ """
38
+ __slots__ = ("id", "display_name", "total_users", "updated_at", "group_detail")
39
+ ID_FIELD_NUMBER: _ClassVar[int]
40
+ DISPLAY_NAME_FIELD_NUMBER: _ClassVar[int]
41
+ TOTAL_USERS_FIELD_NUMBER: _ClassVar[int]
42
+ UPDATED_AT_FIELD_NUMBER: _ClassVar[int]
43
+ GROUP_DETAIL_FIELD_NUMBER: _ClassVar[int]
44
+ id: str | None
45
+ display_name: str | None
46
+ total_users: int | None
47
+ updated_at: _timestamp_pb2.Timestamp | None
48
+ group_detail: str | None
49
+ def __init__(self): ...
50
+
51
+
52
+ class ListDirUsersResponse(_message.Message):
53
+ __slots__ = ("users", "total_size", "next_page_token", "prev_page_token")
54
+ USERS_FIELD_NUMBER: _ClassVar[int]
55
+ TOTAL_SIZE_FIELD_NUMBER: _ClassVar[int]
56
+ NEXT_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
57
+ PREV_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
58
+ users: _containers.RepeatedCompositeFieldContainer[DirUser]
59
+ total_size: int
60
+ next_page_token: str
61
+ prev_page_token: str
62
+
63
+ def __init__(self, users: _Optional[_Iterable[_Union[DirUser, _Mapping]]] = ...,
64
+ total_size: _Optional[int] = ..., next_page_token: _Optional[str] = ...,
65
+ prev_page_token: _Optional[str] = ...) -> None: ...
66
+
67
+
68
+ class ListDirGroupsResponse(_message.Message):
69
+ __slots__ = ("groups", "total_size", "next_page_token", "prev_page_token")
70
+ GROUPS_FIELD_NUMBER: _ClassVar[int]
71
+ TOTAL_SIZE_FIELD_NUMBER: _ClassVar[int]
72
+ NEXT_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
73
+ PREV_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int]
74
+ groups: _containers.RepeatedCompositeFieldContainer[DirGroup]
75
+ total_size: int
76
+ next_page_token: str
77
+ prev_page_token: str
78
+
79
+ def __init__(self, groups: _Optional[_Iterable[_Union[DirGroup, _Mapping]]] = ...,
80
+ total_size: _Optional[int] = ..., next_page_token: _Optional[str] = ...,
81
+ prev_page_token: _Optional[str] = ...) -> None: ...