ecodev-core 0.0.53__tar.gz → 0.0.55__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.

Potentially problematic release.


This version of ecodev-core might be problematic. Click here for more details.

Files changed (33) hide show
  1. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/PKG-INFO +2 -2
  2. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/__init__.py +3 -1
  3. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/authentication.py +40 -2
  4. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/db_upsertion.py +3 -1
  5. ecodev_core-0.0.55/ecodev_core/token_banlist.py +18 -0
  6. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/pyproject.toml +2 -2
  7. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/LICENSE.md +0 -0
  8. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/README.md +0 -0
  9. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/app_activity.py +0 -0
  10. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/app_rights.py +0 -0
  11. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/app_user.py +0 -0
  12. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/auth_configuration.py +0 -0
  13. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/backup.py +0 -0
  14. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/check_dependencies.py +0 -0
  15. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/custom_equal.py +0 -0
  16. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/db_connection.py +0 -0
  17. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/db_filters.py +0 -0
  18. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/db_insertion.py +0 -0
  19. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/db_retrieval.py +0 -0
  20. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/deployment.py +0 -0
  21. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/email_sender.py +0 -0
  22. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/enum_utils.py +0 -0
  23. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/es_connection.py +0 -0
  24. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/list_utils.py +0 -0
  25. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/logger.py +0 -0
  26. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/pandas_utils.py +0 -0
  27. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/permissions.py +0 -0
  28. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/pydantic_utils.py +0 -0
  29. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/read_write.py +0 -0
  30. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/safe_utils.py +0 -0
  31. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/settings.py +0 -0
  32. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/sqlmodel_utils.py +0 -0
  33. {ecodev_core-0.0.53 → ecodev_core-0.0.55}/ecodev_core/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ecodev-core
3
- Version: 0.0.53
3
+ Version: 0.0.55
4
4
  Summary: Low level sqlmodel/fastapi/pydantic building blocks
5
5
  License: MIT
6
6
  Author: Thomas Epelbaum
@@ -43,7 +43,7 @@ Requires-Dist: pandas (>=2,<3)
43
43
  Requires-Dist: passlib[bcyrypt] (>=1,<2)
44
44
  Requires-Dist: progressbar2 (>=4.4.2,<5.0.0)
45
45
  Requires-Dist: psycopg2-binary (>=2,<3)
46
- Requires-Dist: pydantic (==2.9.2)
46
+ Requires-Dist: pydantic (==2.11.7)
47
47
  Requires-Dist: pydantic-settings (>=2,<3)
48
48
  Requires-Dist: python-jose[cryptography] (>=3,<4)
49
49
  Requires-Dist: pyyaml (>=6,<7)
@@ -12,6 +12,7 @@ from ecodev_core.app_user import select_user
12
12
  from ecodev_core.app_user import upsert_app_users
13
13
  from ecodev_core.auth_configuration import AUTH
14
14
  from ecodev_core.authentication import attempt_to_log
15
+ from ecodev_core.authentication import ban_token
15
16
  from ecodev_core.authentication import get_access_token
16
17
  from ecodev_core.authentication import get_app_services
17
18
  from ecodev_core.authentication import get_current_user
@@ -88,6 +89,7 @@ from ecodev_core.safe_utils import SimpleReturn
88
89
  from ecodev_core.safe_utils import stringify
89
90
  from ecodev_core.settings import SETTINGS
90
91
  from ecodev_core.settings import Settings
92
+ from ecodev_core.token_banlist import TokenBanlist
91
93
  from ecodev_core.version import db_to_value
92
94
  from ecodev_core.version import get_row_versions
93
95
  from ecodev_core.version import get_versions
@@ -108,4 +110,4 @@ __all__ = [
108
110
  'sort_by_keys', 'sort_by_values', 'Settings', 'load_yaml_file', 'Deployment', 'Version',
109
111
  'sfield', 'field', 'upsert_df_data', 'upsert_deletor', 'get_row_versions', 'get_versions',
110
112
  'db_to_value', 'upsert_data', 'upsert_selector', 'get_sfield_columns', 'filter_to_sfield_dict',
111
- 'SETTINGS', 'add_missing_enum_values']
113
+ 'SETTINGS', 'add_missing_enum_values', 'ban_token', 'TokenBanlist']
@@ -33,7 +33,7 @@ from ecodev_core.db_connection import engine
33
33
  from ecodev_core.logger import logger_get
34
34
  from ecodev_core.permissions import Permission
35
35
  from ecodev_core.pydantic_utils import Frozen
36
-
36
+ from ecodev_core.token_banlist import TokenBanlist
37
37
 
38
38
  SCHEME = OAuth2PasswordBearer(tokenUrl='login')
39
39
  auth_router = APIRouter(tags=['authentication'])
@@ -44,6 +44,7 @@ INVALID_USER = 'Invalid User'
44
44
  INVALID_TFA = 'Invalid TFA code'
45
45
  ADMIN_ERROR = 'Could not validate credentials. You need admin rights to call this'
46
46
  INVALID_CREDENTIALS = 'Invalid Credentials'
47
+ REVOKED_TOKEN = 'This token has been revoked (by a logout action), please login again.'
47
48
  log = logger_get(__name__)
48
49
 
49
50
 
@@ -62,7 +63,7 @@ class TokenData(Frozen):
62
63
  id: int
63
64
 
64
65
 
65
- def get_access_token(token: Dict[str, Any]):
66
+ def get_access_token(token: Dict[str, Any]) -> str | None:
66
67
  """
67
68
  Robust method to return access token or None
68
69
  """
@@ -158,6 +159,9 @@ def is_authorized_user(token: str = Depends(SCHEME)) -> bool:
158
159
  """
159
160
  Check if the passed token corresponds to an authorized user
160
161
  """
162
+ if _is_banned(token):
163
+ return False
164
+
161
165
  try:
162
166
  return get_current_user(token) is not None
163
167
  except Exception:
@@ -180,12 +184,38 @@ def get_user(token: str = Depends(SCHEME),
180
184
  """
181
185
  Retrieves (if it exists) the db user corresponding to the passed token
182
186
  """
187
+ if _is_banned(token):
188
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=REVOKED_TOKEN,
189
+ headers={'WWW-Authenticate': 'Bearer'})
183
190
  if user := get_current_user(token, tfa_value, tfa_check):
184
191
  return user
185
192
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=INVALID_CREDENTIALS,
186
193
  headers={'WWW-Authenticate': 'Bearer'})
187
194
 
188
195
 
196
+ def ban_token(token: str, session: Session) -> None:
197
+ """
198
+ Ban the passed token
199
+ """
200
+ session.add(TokenBanlist(token=token))
201
+ session.commit()
202
+
203
+
204
+ def _is_banned(token: str) -> bool:
205
+ """
206
+ Check if the passed token is banned.
207
+
208
+ NB: Clean the TokenBanlist table (deleting old entries) on the fly
209
+ """
210
+ with Session(engine) as session:
211
+ threshold = datetime.now() - timedelta(minutes=EXPIRATION_LENGTH)
212
+ for token_banned in session.exec(
213
+ select(TokenBanlist).where(TokenBanlist.created_at <= threshold)).all():
214
+ session.delete(token_banned)
215
+ session.commit()
216
+ return token in session.exec(select(TokenBanlist.token)).all()
217
+
218
+
189
219
  def get_current_user(token: str,
190
220
  tfa_value: Optional[str] = None,
191
221
  tfa_check: bool = False
@@ -202,6 +232,10 @@ def is_admin_user(token: str = Depends(SCHEME)) -> AppUser:
202
232
  """
203
233
  Retrieves (if it exists) the admin (meaning who has valid credentials) user from the db
204
234
  """
235
+ if _is_banned(token):
236
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=REVOKED_TOKEN,
237
+ headers={'WWW-Authenticate': 'Bearer'})
238
+
205
239
  if (user := get_current_user(token)) and user.permission == Permission.ADMIN:
206
240
  return user
207
241
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ADMIN_ERROR,
@@ -212,6 +246,10 @@ def is_monitoring_user(token: str = Depends(SCHEME)) -> AppUser:
212
246
  """
213
247
  Retrieves (if it exists) the monitoring user from the db
214
248
  """
249
+ if _is_banned(token):
250
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=REVOKED_TOKEN,
251
+ headers={'WWW-Authenticate': 'Bearer'})
252
+
215
253
  if (user := get_current_user(token)) and user.user == MONITORING:
216
254
  return user
217
255
  raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
@@ -32,7 +32,9 @@ SA_COLUMN_KWARGS = 'sa_column_kwargs'
32
32
  def add_missing_enum_values(enum: EnumType, session: Session, new_vals: list | None = None) -> None:
33
33
  """
34
34
  Add to an existing enum its missing db values. Do so by retrieving what is already in db, and
35
- insert what is new
35
+ insert what is new.
36
+
37
+ NB: new_val argument is there for testing purposes
36
38
  """
37
39
 
38
40
  for val in [e.name for e in new_vals or enum if e.name not in get_enum_values(enum, session)]:
@@ -0,0 +1,18 @@
1
+ """
2
+ Module implementing the token ban list table
3
+ """
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ from sqlmodel import Field
8
+ from sqlmodel import SQLModel
9
+
10
+
11
+ class TokenBanlist(SQLModel, table=True): # type: ignore
12
+ """
13
+ A token banlist: timestamped banned token.
14
+ """
15
+ __tablename__ = 'token_banlist'
16
+ id: Optional[int] = Field(default=None, primary_key=True)
17
+ created_at: datetime = Field(default_factory=datetime.utcnow)
18
+ token: str = Field(index=True)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ecodev-core"
3
- version = "0.0.53"
3
+ version = "0.0.55"
4
4
  description = "Low level sqlmodel/fastapi/pydantic building blocks"
5
5
  authors = ["Thomas Epelbaum <tomepel@gmail.com>",
6
6
  "Olivier Gabriel <olivier.gabriel.geom@gmail.com>",
@@ -52,7 +52,7 @@ pandas = "~2"
52
52
  passlib = {version = "~1", extras = ["bcyrypt"]}
53
53
  progressbar2 = "^4.4.2"
54
54
  psycopg2-binary = "~2"
55
- pydantic = "2.9.2"
55
+ pydantic = "2.11.7"
56
56
  pydantic-settings = "~2"
57
57
  python-jose = {version = "~3", extras = ["cryptography"]}
58
58
  pyyaml = "~6"
File without changes
File without changes