usso 0.18.0__tar.gz → 0.20.0__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.
- {usso-0.18.0/src/usso.egg-info → usso-0.20.0}/PKG-INFO +1 -1
- {usso-0.18.0 → usso-0.20.0}/pyproject.toml +1 -1
- usso-0.20.0/src/usso/__init__.py +3 -0
- usso-0.20.0/src/usso/api.py +177 -0
- {usso-0.18.0 → usso-0.20.0/src/usso.egg-info}/PKG-INFO +1 -1
- {usso-0.18.0 → usso-0.20.0}/src/usso.egg-info/SOURCES.txt +2 -0
- usso-0.20.0/tests/test_api.py +67 -0
- usso-0.20.0/tests/test_core.py +76 -0
- {usso-0.18.0 → usso-0.20.0}/tests/test_simple.py +5 -2
- usso-0.18.0/src/usso/__init__.py +0 -3
- usso-0.18.0/src/usso/api.py +0 -108
- {usso-0.18.0 → usso-0.20.0}/LICENSE.txt +0 -0
- {usso-0.18.0 → usso-0.20.0}/README.md +0 -0
- {usso-0.18.0 → usso-0.20.0}/setup.cfg +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso/b64tools.py +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso/core.py +2 -2
- {usso-0.18.0 → usso-0.20.0}/src/usso/exceptions.py +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso/fastapi/__init__.py +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso/fastapi/integration.py +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso/package_data.dat +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso.egg-info/dependency_links.txt +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso.egg-info/entry_points.txt +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso.egg-info/requires.txt +0 -0
- {usso-0.18.0 → usso-0.20.0}/src/usso.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: usso
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.0
|
4
4
|
Summary: A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices.
|
5
5
|
Author-email: Mahdi Kiani <mahdikiany@gmail.com>
|
6
6
|
Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "usso"
|
7
|
-
version = "0.
|
7
|
+
version = "0.20.0"
|
8
8
|
description = "A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices."
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.8"
|
@@ -0,0 +1,177 @@
|
|
1
|
+
import requests
|
2
|
+
from singleton import Singleton
|
3
|
+
|
4
|
+
from usso.core import UserData, Usso
|
5
|
+
|
6
|
+
|
7
|
+
class UssoAPI(metaclass=Singleton):
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
url: str = "https://api.usso.io",
|
11
|
+
api_key: str = None,
|
12
|
+
refresh_token: str = None,
|
13
|
+
):
|
14
|
+
if url and not url.startswith("http"):
|
15
|
+
url = f"https://{url}"
|
16
|
+
self.url = url
|
17
|
+
assert (
|
18
|
+
api_key or refresh_token
|
19
|
+
), "Either api_key or refresh_token must be provided"
|
20
|
+
self.api_key = api_key
|
21
|
+
self.refresh_token = refresh_token
|
22
|
+
self.access_token = None
|
23
|
+
|
24
|
+
def _refresh(self):
|
25
|
+
if not self.refresh_token:
|
26
|
+
return
|
27
|
+
|
28
|
+
url = f"{self.url}/auth/refresh"
|
29
|
+
|
30
|
+
if self.refresh_token:
|
31
|
+
headers = {
|
32
|
+
"Authorization": f"Bearer {self.refresh_token}",
|
33
|
+
"content-type": "application/json",
|
34
|
+
}
|
35
|
+
|
36
|
+
resp = requests.post(url, headers=headers)
|
37
|
+
self.access_token = resp.json().get("access_token")
|
38
|
+
|
39
|
+
def _access_valid(self) -> bool:
|
40
|
+
if not self.access_token:
|
41
|
+
return False
|
42
|
+
|
43
|
+
user_data = Usso(
|
44
|
+
jwks_url=f"{self.url}/website/jwks.json?"
|
45
|
+
).user_data_from_token(self.access_token)
|
46
|
+
if user_data:
|
47
|
+
return True
|
48
|
+
return False
|
49
|
+
|
50
|
+
def _request(
|
51
|
+
self,
|
52
|
+
method="get",
|
53
|
+
endpoint: str = "",
|
54
|
+
data: dict = None,
|
55
|
+
**kwargs,
|
56
|
+
) -> dict:
|
57
|
+
url = f"{self.url}/{endpoint}"
|
58
|
+
headers = {"content-type": "application/json"}
|
59
|
+
if self.api_key:
|
60
|
+
headers["x-api-key"] = self.api_key
|
61
|
+
elif self.refresh_token:
|
62
|
+
if not self.access_token:
|
63
|
+
self._refresh()
|
64
|
+
headers["Authorization"] = f"Bearer {self.access_token}"
|
65
|
+
|
66
|
+
resp = requests.request(
|
67
|
+
method,
|
68
|
+
url,
|
69
|
+
headers=headers,
|
70
|
+
json=data,
|
71
|
+
)
|
72
|
+
if kwargs.get("raise", True):
|
73
|
+
resp.raise_for_status()
|
74
|
+
return resp.json()
|
75
|
+
|
76
|
+
def get_users(self, **kwargs) -> list[UserData]:
|
77
|
+
users_dict = self._request(endpoint="website/users", **kwargs)
|
78
|
+
|
79
|
+
return [
|
80
|
+
UserData(user_id=user.get("uid"), **user) for user in users_dict
|
81
|
+
]
|
82
|
+
|
83
|
+
def get_user(self, user_id: str, **kwargs) -> UserData:
|
84
|
+
return UserData(
|
85
|
+
**self._request(
|
86
|
+
endpoint=f"website/users/{user_id}",
|
87
|
+
**kwargs,
|
88
|
+
)
|
89
|
+
)
|
90
|
+
|
91
|
+
def get_user_credentials(self, user_id: str, **kwargs) -> UserData:
|
92
|
+
return UserData(
|
93
|
+
**self._request(
|
94
|
+
endpoint=f"website/users/{user_id}/credentials",
|
95
|
+
**kwargs,
|
96
|
+
)
|
97
|
+
)
|
98
|
+
|
99
|
+
def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
|
100
|
+
return UserData(
|
101
|
+
**self._request(
|
102
|
+
endpoint="website/users/credentials",
|
103
|
+
data=credentials,
|
104
|
+
**kwargs,
|
105
|
+
)
|
106
|
+
)
|
107
|
+
|
108
|
+
def create_user(self, user_data: dict, **kwargs) -> UserData:
|
109
|
+
return UserData(
|
110
|
+
**self._request(
|
111
|
+
method="post",
|
112
|
+
endpoint="website/users",
|
113
|
+
data=user_data,
|
114
|
+
**kwargs,
|
115
|
+
)
|
116
|
+
)
|
117
|
+
|
118
|
+
def create_user_credentials(
|
119
|
+
self, user_id: str, credentials: dict, **kwargs
|
120
|
+
) -> UserData:
|
121
|
+
return UserData(
|
122
|
+
**self._request(
|
123
|
+
method="post",
|
124
|
+
endpoint=f"website/users/{user_id}/credentials",
|
125
|
+
data=credentials,
|
126
|
+
**kwargs,
|
127
|
+
)
|
128
|
+
)
|
129
|
+
|
130
|
+
def create_user_by_credentials(
|
131
|
+
self,
|
132
|
+
user_data: dict | None = None,
|
133
|
+
credentials: dict | None = None,
|
134
|
+
**kwargs,
|
135
|
+
) -> UserData:
|
136
|
+
|
137
|
+
if credentials:
|
138
|
+
user_data["authenticators"] = [credentials]
|
139
|
+
return UserData(
|
140
|
+
**self._request(
|
141
|
+
method="post",
|
142
|
+
endpoint="website/users",
|
143
|
+
data=user_data,
|
144
|
+
**kwargs,
|
145
|
+
)
|
146
|
+
)
|
147
|
+
|
148
|
+
def get_user_payload(self, user_id: str, **kwargs) -> dict:
|
149
|
+
return self._request(
|
150
|
+
endpoint=f"website/users/{user_id}/payload", **kwargs
|
151
|
+
)
|
152
|
+
|
153
|
+
def update_user_payload(
|
154
|
+
self,
|
155
|
+
user_id: str,
|
156
|
+
payload: dict,
|
157
|
+
**kwargs,
|
158
|
+
) -> dict:
|
159
|
+
return self._request(
|
160
|
+
method="patch",
|
161
|
+
endpoint=f"website/users/{user_id}/payload",
|
162
|
+
data=payload,
|
163
|
+
**kwargs,
|
164
|
+
)
|
165
|
+
|
166
|
+
def set_user_payload(
|
167
|
+
self,
|
168
|
+
user_id: str,
|
169
|
+
payload: dict,
|
170
|
+
**kwargs,
|
171
|
+
) -> dict:
|
172
|
+
return self._request(
|
173
|
+
method="put",
|
174
|
+
endpoint=f"website/users/{user_id}/payload",
|
175
|
+
data=payload,
|
176
|
+
**kwargs,
|
177
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: usso
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.0
|
4
4
|
Summary: A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices.
|
5
5
|
Author-email: Mahdi Kiani <mahdikiany@gmail.com>
|
6
6
|
Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import os
|
2
|
+
import unittest
|
3
|
+
|
4
|
+
from usso.api import UssoAPI
|
5
|
+
|
6
|
+
|
7
|
+
class TestAPI(unittest.TestCase):
|
8
|
+
def get_usso(self):
|
9
|
+
return UssoAPI(
|
10
|
+
url="https://sso.usso.io",
|
11
|
+
api_key=os.getenv("USSO_API_KEY"),
|
12
|
+
)
|
13
|
+
|
14
|
+
def test_get_users(self):
|
15
|
+
usso_api = self.get_usso()
|
16
|
+
users = usso_api.get_users()
|
17
|
+
self.assertIsInstance(users, list)
|
18
|
+
for user in users:
|
19
|
+
self.assertIsInstance(user, dict)
|
20
|
+
self.assertIn("user_id", user)
|
21
|
+
self.assertIn("username", user)
|
22
|
+
self.assertIn("email", user)
|
23
|
+
return users
|
24
|
+
|
25
|
+
def test_get_user(self):
|
26
|
+
users = self.test_get_users()
|
27
|
+
if len(users) == 0:
|
28
|
+
self.skipTest("No users found")
|
29
|
+
user = users[0]
|
30
|
+
usso_api = self.get_usso()
|
31
|
+
user = usso_api.get_user(user["user_id"])
|
32
|
+
self.assertIsInstance(user, dict)
|
33
|
+
self.assertIn("user_id", user)
|
34
|
+
self.assertIn("username", user)
|
35
|
+
self.assertIn("email", user)
|
36
|
+
return user
|
37
|
+
|
38
|
+
def test_get_user_credentials(self):
|
39
|
+
users = self.test_get_users()
|
40
|
+
if len(users) == 0:
|
41
|
+
self.skipTest("No users found")
|
42
|
+
user = users[0]
|
43
|
+
usso_api = self.get_usso()
|
44
|
+
credentials = usso_api.get_user_credentials(user["user_id"])
|
45
|
+
self.assertIsInstance(credentials, list)
|
46
|
+
for credential in credentials:
|
47
|
+
self.assertIsInstance(credential, dict)
|
48
|
+
self.assertIn("credential_id", credential)
|
49
|
+
self.assertIn("type", credential)
|
50
|
+
self.assertIn("created_at", credential)
|
51
|
+
return credentials
|
52
|
+
|
53
|
+
def test_get_user_by_credentials(self):
|
54
|
+
credentials = self.test_get_user_credentials()
|
55
|
+
if len(credentials) == 0:
|
56
|
+
self.skipTest("No credentials found")
|
57
|
+
credential = credentials[0]
|
58
|
+
usso_api = self.get_usso()
|
59
|
+
user = usso_api.get_user_by_credentials(credential)
|
60
|
+
self.assertIsInstance(user, dict)
|
61
|
+
self.assertIn("user_id", user)
|
62
|
+
self.assertIn("username", user)
|
63
|
+
self.assertIn("email", user)
|
64
|
+
return user
|
65
|
+
|
66
|
+
if __name__ == "__main__":
|
67
|
+
unittest.main()
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import unittest
|
2
|
+
import uuid
|
3
|
+
|
4
|
+
from usso.core import user_data_from_token
|
5
|
+
from usso.exceptions import USSOException
|
6
|
+
|
7
|
+
|
8
|
+
def generate_expired_token():
|
9
|
+
# Your code to generate an expired token goes here
|
10
|
+
pass
|
11
|
+
|
12
|
+
|
13
|
+
# Generate an invalid token for testing
|
14
|
+
def generate_invalid_token():
|
15
|
+
# Your code to generate an invalid token goes here
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
# Generate a valid token for testing
|
20
|
+
def generate_valid_token():
|
21
|
+
# Your code to generate a valid token goes here
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class TestCore(unittest.TestCase):
|
26
|
+
def test_user_data_from_token_valid_token(self):
|
27
|
+
# Generate a valid token for testing
|
28
|
+
valid_token = generate_valid_token()
|
29
|
+
|
30
|
+
# Call the user_data_from_token function with the valid token
|
31
|
+
user_data = user_data_from_token(valid_token)
|
32
|
+
|
33
|
+
# Assert that the user_data is not None
|
34
|
+
self.assertIsNotNone(user_data)
|
35
|
+
|
36
|
+
# Assert that the user_data has the expected attributes
|
37
|
+
self.assertEqual(user_data.uid, uuid.UUID(""))
|
38
|
+
self.assertEqual(user_data.token, valid_token)
|
39
|
+
# Add more assertions for other attributes
|
40
|
+
# Generate an expired token for testing
|
41
|
+
|
42
|
+
def test_user_data_from_token_expired_token(self):
|
43
|
+
# Generate an expired token for testing
|
44
|
+
expired_token = generate_expired_token()
|
45
|
+
|
46
|
+
# Call the user_data_from_token function with the expired token
|
47
|
+
user_data = user_data_from_token(expired_token)
|
48
|
+
|
49
|
+
# Assert that the user_data is None
|
50
|
+
self.assertIsNone(user_data)
|
51
|
+
|
52
|
+
# Assert that the USSOException is raised with the expected error
|
53
|
+
with self.assertRaises(USSOException) as context:
|
54
|
+
user_data_from_token(expired_token, raise_exception=True)
|
55
|
+
self.assertEqual(context.exception.error, "expired_signature")
|
56
|
+
|
57
|
+
def test_user_data_from_token_invalid_token(self):
|
58
|
+
# Generate an invalid token for testing
|
59
|
+
invalid_token = generate_invalid_token()
|
60
|
+
|
61
|
+
# Call the user_data_from_token function with the invalid token
|
62
|
+
user_data = user_data_from_token(invalid_token)
|
63
|
+
|
64
|
+
# Assert that the user_data is None
|
65
|
+
self.assertIsNone(user_data)
|
66
|
+
|
67
|
+
# Assert that the USSOException is raised with the expected error
|
68
|
+
with self.assertRaises(USSOException) as context:
|
69
|
+
user_data_from_token(invalid_token, raise_exception=True)
|
70
|
+
self.assertEqual(context.exception.error, "invalid_signature")
|
71
|
+
|
72
|
+
# Add more test cases for other scenarios
|
73
|
+
|
74
|
+
|
75
|
+
if __name__ == "__main__":
|
76
|
+
unittest.main()
|
usso-0.18.0/src/usso/__init__.py
DELETED
usso-0.18.0/src/usso/api.py
DELETED
@@ -1,108 +0,0 @@
|
|
1
|
-
import requests
|
2
|
-
from singleton import Singleton
|
3
|
-
|
4
|
-
from usso.core import Usso
|
5
|
-
|
6
|
-
|
7
|
-
class UssoAPI(metaclass=Singleton):
|
8
|
-
def __init__(
|
9
|
-
self,
|
10
|
-
url: str = "https://api.usso.io",
|
11
|
-
api_key: str = None,
|
12
|
-
refresh_token: str = None,
|
13
|
-
):
|
14
|
-
self.url = url
|
15
|
-
assert (
|
16
|
-
api_key or refresh_token
|
17
|
-
), "Either api_key or refresh_token must be provided"
|
18
|
-
self.api_key = api_key
|
19
|
-
self.refresh_token = refresh_token
|
20
|
-
self.access_token = None
|
21
|
-
|
22
|
-
def refresh(self):
|
23
|
-
if not self.refresh_token:
|
24
|
-
return
|
25
|
-
|
26
|
-
url = f"{self.url}/auth/refresh"
|
27
|
-
|
28
|
-
if self.refresh_token:
|
29
|
-
headers = {"Authorization": f"Bearer {self.refresh_token}"}
|
30
|
-
|
31
|
-
resp = requests.post(url, headers=headers)
|
32
|
-
self.access_token = resp.json().get("access_token")
|
33
|
-
|
34
|
-
def _access_valid(self):
|
35
|
-
if not self.access_token:
|
36
|
-
return False
|
37
|
-
|
38
|
-
user_data = Usso(
|
39
|
-
jwks_url=f"{self.url}/website/jwks.json?"
|
40
|
-
).user_data_from_token(self.access_token)
|
41
|
-
if user_data:
|
42
|
-
return True
|
43
|
-
return False
|
44
|
-
|
45
|
-
def _request(self, method="get", endpoint: str = "", data: dict = None):
|
46
|
-
url = f"{self.url}/{endpoint}"
|
47
|
-
headers = {}
|
48
|
-
if self.api_key:
|
49
|
-
headers["x-api-key"] = self.api_key
|
50
|
-
elif self.refresh_token:
|
51
|
-
if not self.access_token:
|
52
|
-
self.refresh()
|
53
|
-
headers["Authorization"] = f"Bearer {self.access_token}"
|
54
|
-
|
55
|
-
resp = requests.request(method, url, headers=headers, json=data)
|
56
|
-
return resp.json()
|
57
|
-
|
58
|
-
def get_users(self):
|
59
|
-
return self._request(endpoint="website/users/")
|
60
|
-
|
61
|
-
def get_user(self, user_id: str):
|
62
|
-
return self._request(endpoint=f"website/users/{user_id}/")
|
63
|
-
|
64
|
-
def get_user_credentials(self, user_id: str):
|
65
|
-
return self._request(endpoint=f"website/users/{user_id}/credentials/")
|
66
|
-
|
67
|
-
def get_user_by_credentials(self, credentials: dict):
|
68
|
-
return self._request(
|
69
|
-
endpoint="website/users/credentials/", data=credentials
|
70
|
-
)
|
71
|
-
|
72
|
-
def create_user(self, user_data: dict):
|
73
|
-
return self._request(
|
74
|
-
method="post", endpoint="website/users/", data=user_data
|
75
|
-
)
|
76
|
-
|
77
|
-
def create_user_credentials(self, user_id: str, credentials: dict):
|
78
|
-
return self._request(
|
79
|
-
method="post",
|
80
|
-
endpoint=f"website/users/{user_id}/credentials/",
|
81
|
-
data=credentials,
|
82
|
-
)
|
83
|
-
|
84
|
-
def create_user_by_credentials(
|
85
|
-
self, user_data: dict, credentials: dict | None = None
|
86
|
-
):
|
87
|
-
if credentials:
|
88
|
-
user_data["authenticators"] = [credentials]
|
89
|
-
return self._request(
|
90
|
-
method="post", endpoint="website/users/", data=user_data
|
91
|
-
)
|
92
|
-
|
93
|
-
def get_user_payload(self, user_id: str):
|
94
|
-
return self._request(endpoint=f"website/users/{user_id}/payload/")
|
95
|
-
|
96
|
-
def update_user_payload(self, user_id: str, payload: dict):
|
97
|
-
return self._request(
|
98
|
-
method="patch",
|
99
|
-
endpoint=f"website/users/{user_id}/payload/",
|
100
|
-
data=payload,
|
101
|
-
)
|
102
|
-
|
103
|
-
def set_user_payload(self, user_id: str, payload: dict):
|
104
|
-
return self._request(
|
105
|
-
method="put",
|
106
|
-
endpoint=f"website/users/{user_id}/payload/",
|
107
|
-
data=payload,
|
108
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,12 +1,12 @@
|
|
1
|
-
import os
|
2
1
|
import logging
|
2
|
+
import os
|
3
3
|
import uuid
|
4
4
|
from functools import lru_cache
|
5
5
|
from typing import Optional, Tuple
|
6
|
-
from singleton import Singleton
|
7
6
|
|
8
7
|
import jwt
|
9
8
|
from pydantic import BaseModel
|
9
|
+
from singleton import Singleton
|
10
10
|
|
11
11
|
from . import b64tools
|
12
12
|
from .exceptions import USSOException
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|