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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.18.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.18.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,3 @@
1
+ from .core import UserData, Usso
2
+
3
+ __all__ = ["UserData", "Usso"]
@@ -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.18.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>
@@ -15,4 +15,6 @@ src/usso.egg-info/requires.txt
15
15
  src/usso.egg-info/top_level.txt
16
16
  src/usso/fastapi/__init__.py
17
17
  src/usso/fastapi/integration.py
18
+ tests/test_api.py
19
+ tests/test_core.py
18
20
  tests/test_simple.py
@@ -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()
@@ -6,8 +6,11 @@ import unittest
6
6
 
7
7
 
8
8
  class TestSimple(unittest.TestCase):
9
- def test_add_one(self):
10
- self.assertEqual(5 + 1, 6)
9
+ def test_import(self):
10
+ import usso
11
+
12
+ usso.Usso()
13
+ usso.UserData()
11
14
 
12
15
 
13
16
  if __name__ == "__main__":
@@ -1,3 +0,0 @@
1
- from .core import UserData, user_data_from_token
2
-
3
- __all__ = ["UserData", "user_data_from_token"]
@@ -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