scalekit-sdk-python 1.0.2__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 (124) hide show
  1. {scalekit_sdk_python-1.0.2/scalekit_sdk_python.egg-info → scalekit_sdk_python-1.0.4}/PKG-INFO +12 -12
  2. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/__init__.py +2 -1
  3. scalekit_sdk_python-1.0.4/scalekit/client.py +291 -0
  4. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/common/scalekit.py +10 -0
  5. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/core.py +3 -1
  6. scalekit_sdk_python-1.0.4/scalekit/directory.py +243 -0
  7. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/organization.py +20 -1
  8. scalekit_sdk_python-1.0.4/scalekit/utils/directory.py +81 -0
  9. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2.py +27 -25
  10. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2.pyi +8 -4
  11. scalekit_sdk_python-1.0.4/scalekit/v1/commons/commons_pb2.py +62 -0
  12. scalekit_sdk_python-1.0.4/scalekit/v1/commons/commons_pb2.pyi +83 -0
  13. scalekit_sdk_python-1.0.4/scalekit/v1/connections/connections_pb2.py +450 -0
  14. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/connections/connections_pb2.pyi +70 -8
  15. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/connections/connections_pb2_grpc.py +33 -0
  16. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2.py +342 -0
  17. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2.pyi +414 -0
  18. scalekit_sdk_python-1.0.4/scalekit/v1/directories/directories_pb2_grpc.py +429 -0
  19. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2.py +65 -23
  20. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2.pyi +61 -1
  21. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/environments/environments_pb2_grpc.py +165 -0
  22. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2.py +105 -0
  23. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2.pyi +279 -0
  24. scalekit_sdk_python-1.0.4/scalekit/v1/events/events_pb2_grpc.py +66 -0
  25. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2.py +69 -0
  26. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2.pyi +74 -0
  27. scalekit_sdk_python-1.0.4/scalekit/v1/login_box/login_box_pb2_grpc.py +132 -0
  28. scalekit_sdk_python-1.0.4/scalekit/v1/members/members_pb2.py +108 -0
  29. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/members/members_pb2.pyi +11 -4
  30. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2.py +48 -0
  31. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2.pyi +37 -0
  32. scalekit_sdk_python-1.0.4/scalekit/v1/migrations/migrations_pb2_grpc.py +133 -0
  33. scalekit_sdk_python-1.0.4/scalekit/v1/organizations/__init__.py +0 -0
  34. scalekit_sdk_python-1.0.4/scalekit/v1/organizations/organizations_pb2.py +217 -0
  35. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/organizations/organizations_pb2.pyi +41 -8
  36. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/organizations/organizations_pb2_grpc.py +33 -0
  37. scalekit_sdk_python-1.0.4/scalekit/v1/roles/__init__.py +0 -0
  38. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2.py +109 -0
  39. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2.pyi +116 -0
  40. scalekit_sdk_python-1.0.4/scalekit/v1/roles/roles_pb2_grpc.py +199 -0
  41. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/__init__.py +0 -0
  42. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2.py +95 -0
  43. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2.pyi +111 -0
  44. scalekit_sdk_python-1.0.4/scalekit/v1/user_attributes/user_attributes_pb2_grpc.py +298 -0
  45. scalekit_sdk_python-1.0.4/scalekit/v1/users/__init__.py +0 -0
  46. scalekit_sdk_python-1.0.4/scalekit/v1/users/users_pb2.py +104 -0
  47. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/users/users_pb2.pyi +49 -18
  48. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/users/users_pb2_grpc.py +33 -0
  49. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/__init__.py +0 -0
  50. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2.py +43 -0
  51. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2.pyi +31 -0
  52. scalekit_sdk_python-1.0.4/scalekit/v1/webhooks/webhooks_pb2_grpc.py +100 -0
  53. scalekit_sdk_python-1.0.4/scalekit/v1/workspaces/__init__.py +0 -0
  54. scalekit_sdk_python-1.0.4/scalekit/v1/workspaces/workspaces_pb2.py +130 -0
  55. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/workspaces/workspaces_pb2.pyi +74 -1
  56. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/workspaces/workspaces_pb2_grpc.py +133 -0
  57. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4/scalekit_sdk_python.egg-info}/PKG-INFO +12 -12
  58. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/SOURCES.txt +27 -4
  59. scalekit_sdk_python-1.0.4/scalekit_sdk_python.egg-info/requires.txt +10 -0
  60. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/setup.py +12 -12
  61. scalekit_sdk_python-1.0.2/scalekit/client.py +0 -145
  62. scalekit_sdk_python-1.0.2/scalekit/v1/commons/commons_pb2.py +0 -36
  63. scalekit_sdk_python-1.0.2/scalekit/v1/commons/commons_pb2.pyi +0 -30
  64. scalekit_sdk_python-1.0.2/scalekit/v1/connections/connections_pb2.py +0 -409
  65. scalekit_sdk_python-1.0.2/scalekit/v1/events/events_pb2.py +0 -39
  66. scalekit_sdk_python-1.0.2/scalekit/v1/events/events_pb2.pyi +0 -62
  67. scalekit_sdk_python-1.0.2/scalekit/v1/events/events_pb2_grpc.py +0 -4
  68. scalekit_sdk_python-1.0.2/scalekit/v1/members/members_pb2.py +0 -105
  69. scalekit_sdk_python-1.0.2/scalekit/v1/organizations/organizations_pb2.py +0 -188
  70. scalekit_sdk_python-1.0.2/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2.py +0 -83
  71. scalekit_sdk_python-1.0.2/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2.pyi +0 -99
  72. scalekit_sdk_python-1.0.2/scalekit/v1/user_profile_attributes/user_profile_attributes_pb2_grpc.py +0 -174
  73. scalekit_sdk_python-1.0.2/scalekit/v1/users/users_pb2.py +0 -97
  74. scalekit_sdk_python-1.0.2/scalekit/v1/workspaces/workspaces_pb2.py +0 -90
  75. scalekit_sdk_python-1.0.2/scalekit_sdk_python.egg-info/requires.txt +0 -10
  76. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/LICENSE +0 -0
  77. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/README.md +0 -0
  78. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/__init__.py +0 -0
  79. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/__init__.py +0 -0
  80. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2.py +0 -0
  81. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2.pyi +0 -0
  82. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/expression_pb2_grpc.py +0 -0
  83. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/priv/__init__.py +0 -0
  84. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2.py +0 -0
  85. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2.pyi +0 -0
  86. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/priv/private_pb2_grpc.py +0 -0
  87. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2.py +0 -0
  88. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2.pyi +0 -0
  89. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/buf/validate/validate_pb2_grpc.py +0 -0
  90. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/common/__init__.py +0 -0
  91. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/common/user.py +0 -0
  92. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/connection.py +0 -0
  93. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/constants/__init__.py +0 -0
  94. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/constants/user.py +0 -0
  95. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/domain.py +0 -0
  96. {scalekit_sdk_python-1.0.2/scalekit/v1 → scalekit_sdk_python-1.0.4/scalekit/utils}/__init__.py +0 -0
  97. {scalekit_sdk_python-1.0.2/scalekit/v1/clients → scalekit_sdk_python-1.0.4/scalekit/v1}/__init__.py +0 -0
  98. {scalekit_sdk_python-1.0.2/scalekit/v1/commons → scalekit_sdk_python-1.0.4/scalekit/v1/clients}/__init__.py +0 -0
  99. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/clients/clients_pb2_grpc.py +0 -0
  100. {scalekit_sdk_python-1.0.2/scalekit/v1/connections → scalekit_sdk_python-1.0.4/scalekit/v1/commons}/__init__.py +0 -0
  101. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/commons/commons_pb2_grpc.py +0 -0
  102. {scalekit_sdk_python-1.0.2/scalekit/v1/domains → scalekit_sdk_python-1.0.4/scalekit/v1/connections}/__init__.py +0 -0
  103. {scalekit_sdk_python-1.0.2/scalekit/v1/environments → scalekit_sdk_python-1.0.4/scalekit/v1/directories}/__init__.py +0 -0
  104. {scalekit_sdk_python-1.0.2/scalekit/v1/errdetails → scalekit_sdk_python-1.0.4/scalekit/v1/domains}/__init__.py +0 -0
  105. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2.py +0 -0
  106. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2.pyi +0 -0
  107. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/domains/domains_pb2_grpc.py +0 -0
  108. {scalekit_sdk_python-1.0.2/scalekit/v1/events → scalekit_sdk_python-1.0.4/scalekit/v1/environments}/__init__.py +0 -0
  109. {scalekit_sdk_python-1.0.2/scalekit/v1/members → scalekit_sdk_python-1.0.4/scalekit/v1/errdetails}/__init__.py +0 -0
  110. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2.py +0 -0
  111. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2.pyi +0 -0
  112. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/errdetails/errdetails_pb2_grpc.py +0 -0
  113. {scalekit_sdk_python-1.0.2/scalekit/v1/options → scalekit_sdk_python-1.0.4/scalekit/v1/events}/__init__.py +0 -0
  114. {scalekit_sdk_python-1.0.2/scalekit/v1/organizations → scalekit_sdk_python-1.0.4/scalekit/v1/login_box}/__init__.py +0 -0
  115. {scalekit_sdk_python-1.0.2/scalekit/v1/user_profile_attributes → scalekit_sdk_python-1.0.4/scalekit/v1/members}/__init__.py +0 -0
  116. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/members/members_pb2_grpc.py +0 -0
  117. {scalekit_sdk_python-1.0.2/scalekit/v1/users → scalekit_sdk_python-1.0.4/scalekit/v1/migrations}/__init__.py +0 -0
  118. {scalekit_sdk_python-1.0.2/scalekit/v1/workspaces → scalekit_sdk_python-1.0.4/scalekit/v1/options}/__init__.py +0 -0
  119. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2.py +0 -0
  120. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2.pyi +0 -0
  121. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit/v1/options/options_pb2_grpc.py +0 -0
  122. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/dependency_links.txt +0 -0
  123. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/scalekit_sdk_python.egg-info/top_level.txt +0 -0
  124. {scalekit_sdk_python-1.0.2 → scalekit_sdk_python-1.0.4}/setup.cfg +0 -0
@@ -1,26 +1,26 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scalekit-sdk-python
3
- Version: 1.0.2
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
7
- Author-email: support@grpc.com
7
+ Author-email: support@scalekit.com
8
8
  License: MIT
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  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']
@@ -0,0 +1,291 @@
1
+
2
+ import json
3
+ from math import floor
4
+ from typing import Any, Optional, Dict
5
+ from urllib.parse import urlencode
6
+
7
+ import jwt
8
+ import hmac
9
+ import hashlib
10
+ import base64
11
+ from datetime import datetime, timedelta, timezone
12
+ from scalekit.core import CoreClient
13
+ from scalekit.domain import DomainClient
14
+ from scalekit.connection import ConnectionClient
15
+ from scalekit.organization import OrganizationClient
16
+ from scalekit.directory import DirectoryClient
17
+ from scalekit.common.scalekit import (
18
+ AuthorizationUrlOptions,
19
+ CodeAuthenticationOptions,
20
+ GrantType,
21
+ IdpInitiatedLoginClaims,
22
+ )
23
+ from scalekit.constants.user import id_token_claim_to_user_map
24
+
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
32
+
33
+
34
+ class ScalekitClient:
35
+ """ Class definition for scalekit client """
36
+
37
+ def __init__(self, env_url: str, client_id: str, client_secret: str):
38
+ """
39
+ Initializer for Scalekit base class
40
+
41
+ :param env_url : Environment URL
42
+ :type : ``` str ```
43
+ :param client_id : Client ID
44
+ :type : ``` str ```
45
+ :param client_secret : Client Secret
46
+ :type : ``` str ```
47
+
48
+ :returns:
49
+ None
50
+ """
51
+ try:
52
+ self.core_client = CoreClient(
53
+ env_url=env_url, client_id=client_id, client_secret=client_secret
54
+ )
55
+ self.domain = DomainClient(self.core_client)
56
+ self.connection = ConnectionClient(self.core_client)
57
+ self.organization = OrganizationClient(self.core_client)
58
+ self.directory = DirectoryClient(self.core_client)
59
+ except Exception as exp:
60
+ raise exp
61
+
62
+ def get_authorization_url(
63
+ self, redirect_uri: str, options: AuthorizationUrlOptions | None
64
+ ):
65
+ """
66
+ Method to get authorization URL
67
+
68
+ :param redirect_uri : Redirect URI for SAML SSO
69
+ :type : ``` str ```
70
+ :param options : Auth URL options object
71
+ :type : ``` obj ```
72
+
73
+ :returns:
74
+ Authorization URL
75
+ """
76
+ try:
77
+ scopes = (
78
+ options.scopes if options.scopes else ["openid", "profile", "email"]
79
+ )
80
+ url_params_dict = {
81
+ "response_type": "code",
82
+ "client_id": self.core_client.client_id,
83
+ "redirect_uri": redirect_uri,
84
+ "scope": " ".join(scopes),
85
+ "state": options.state,
86
+ "nonce": options.nonce,
87
+ "login_hint": options.login_hint,
88
+ "domain_hint": options.domain_hint,
89
+ "connection_id": options.connection_id,
90
+ "organization_id": options.organization_id,
91
+ "provider": options.provider,
92
+ }
93
+
94
+ valid_auth_params = {k: v for k, v in url_params_dict.items() if v}
95
+ query_string = urlencode(valid_auth_params)
96
+
97
+ return f"{self.core_client.env_url}/{AUTHORIZE_ENDPOINT}?{query_string}"
98
+ except Exception as exp:
99
+ raise exp
100
+
101
+ def authenticate_with_code(
102
+ self, code, redirect_uri, options: CodeAuthenticationOptions
103
+ ):
104
+ """
105
+ Method to authenticate with code options
106
+
107
+ :param code : authorization_code
108
+ :type : ``` str ```
109
+ :param redirect_uri : Redirect URI
110
+ :type : ``` str ```
111
+ :param options : CodeAuthenticationOptions Object
112
+ :type : ``` obj ```
113
+
114
+ :returns:
115
+ dict with user, id token & access token
116
+ """
117
+ try:
118
+ response = self.core_client.authenticate(
119
+ json.dumps(
120
+ {
121
+ "code": code,
122
+ "redirect_uri": redirect_uri,
123
+ "grant_type": GrantType.AuthorizationCode.value,
124
+ "client_id": self.core_client.client_id,
125
+ "client_secret": self.core_client.client_secret,
126
+ "code_verifier": options.code_verifier,
127
+ }
128
+ )
129
+ )
130
+ response = json.loads(response.content)
131
+ id_token = response["id_token"]
132
+ access_token = response["access_token"]
133
+ # Validate id_token
134
+ claims = self.__validate_token(id_token, {"verify_aud": False})
135
+ user = {}
136
+ for k, v in claims.items():
137
+ if id_token_claim_to_user_map.get(k, None):
138
+ user[id_token_claim_to_user_map[k]] = v
139
+
140
+ return {"user": user, "id_token": id_token, "access_token": access_token}
141
+
142
+ except Exception as exp:
143
+ raise exp
144
+
145
+ def validate_access_token(self, token: str) -> bool:
146
+ """
147
+ Method to validate access token
148
+
149
+ :param token : access token
150
+ :type : ``` str ```
151
+
152
+ :returns:
153
+ bool
154
+ """
155
+ try:
156
+ self.__validate_token(token)
157
+ return True
158
+ except jwt.exceptions.InvalidTokenError:
159
+ return False
160
+
161
+ def get_idp_initiated_login_claims(self, idp_initiated_login_token: str) -> IdpInitiatedLoginClaims:
162
+ """
163
+ Method to get IDP initiated login claims
164
+
165
+ :param idp_initiated_login_token : IDP initiated login token
166
+ :type : ``` str ```
167
+
168
+ :returns:
169
+ ``` IdpInitiatedLoginClaims ```
170
+ """
171
+ try:
172
+ claims = self.__validate_token(idp_initiated_login_token, {"verify_aud": False})
173
+ return claims
174
+ except Exception as exp:
175
+ raise exp
176
+
177
+ def __validate_token(
178
+ self, token: str, options: Optional[Dict] = None
179
+ ) -> Dict[str, Any]:
180
+ """
181
+ Method to validate token
182
+
183
+ :param token : token
184
+ :type : ``` str ```
185
+
186
+ :returns:
187
+ payload
188
+ """
189
+ self.core_client.get_jwks()
190
+ kid = jwt.get_unverified_header(token)["kid"]
191
+ key = self.core_client.keys[kid]
192
+
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')}"
@@ -48,3 +48,13 @@ class AuthenticationOptions:
48
48
  def __init__(self):
49
49
  """ """
50
50
  self.refresh_token: Optional[str] = None
51
+
52
+
53
+ class IdpInitiatedLoginClaims:
54
+ """Class definition for IDP Initiated Login Claims"""
55
+
56
+ def __init__(self) -> None:
57
+ self.connection_id: str
58
+ self.organization_id: str
59
+ self.login_hint: str
60
+ self.relay_state: Optional[str] = None
@@ -25,7 +25,7 @@ class WithCall(Protocol):
25
25
  class CoreClient:
26
26
  """Class definition for Core Client"""
27
27
 
28
- sdk_version = "Scalekit-Python/1.0.2"
28
+ sdk_version = "Scalekit-Python/1.0.3"
29
29
  api_version = "20240430"
30
30
  user_agent = f"{sdk_version} Python/{platform.python_version()} ({platform.system()}; {platform.architecture()}"
31
31
 
@@ -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
+ )