ecodev-core 0.0.52__py3-none-any.whl → 0.0.54__py3-none-any.whl
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.
- ecodev_core/__init__.py +4 -1
- ecodev_core/authentication.py +40 -2
- ecodev_core/db_upsertion.py +34 -6
- ecodev_core/token_banlist.py +18 -0
- {ecodev_core-0.0.52.dist-info → ecodev_core-0.0.54.dist-info}/METADATA +2 -2
- {ecodev_core-0.0.52.dist-info → ecodev_core-0.0.54.dist-info}/RECORD +8 -7
- {ecodev_core-0.0.52.dist-info → ecodev_core-0.0.54.dist-info}/LICENSE.md +0 -0
- {ecodev_core-0.0.52.dist-info → ecodev_core-0.0.54.dist-info}/WHEEL +0 -0
ecodev_core/__init__.py
CHANGED
|
@@ -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
|
|
@@ -40,6 +41,7 @@ from ecodev_core.db_insertion import get_raw_df
|
|
|
40
41
|
from ecodev_core.db_retrieval import count_rows
|
|
41
42
|
from ecodev_core.db_retrieval import get_rows
|
|
42
43
|
from ecodev_core.db_retrieval import ServerSideField
|
|
44
|
+
from ecodev_core.db_upsertion import add_missing_enum_values
|
|
43
45
|
from ecodev_core.db_upsertion import field
|
|
44
46
|
from ecodev_core.db_upsertion import filter_to_sfield_dict
|
|
45
47
|
from ecodev_core.db_upsertion import get_sfield_columns
|
|
@@ -87,6 +89,7 @@ from ecodev_core.safe_utils import SimpleReturn
|
|
|
87
89
|
from ecodev_core.safe_utils import stringify
|
|
88
90
|
from ecodev_core.settings import SETTINGS
|
|
89
91
|
from ecodev_core.settings import Settings
|
|
92
|
+
from ecodev_core.token_banlist import TokenBanlist
|
|
90
93
|
from ecodev_core.version import db_to_value
|
|
91
94
|
from ecodev_core.version import get_row_versions
|
|
92
95
|
from ecodev_core.version import get_versions
|
|
@@ -107,4 +110,4 @@ __all__ = [
|
|
|
107
110
|
'sort_by_keys', 'sort_by_values', 'Settings', 'load_yaml_file', 'Deployment', 'Version',
|
|
108
111
|
'sfield', 'field', 'upsert_df_data', 'upsert_deletor', 'get_row_versions', 'get_versions',
|
|
109
112
|
'db_to_value', 'upsert_data', 'upsert_selector', 'get_sfield_columns', 'filter_to_sfield_dict',
|
|
110
|
-
'SETTINGS']
|
|
113
|
+
'SETTINGS', 'add_missing_enum_values', 'ban_token', 'TokenBanlist']
|
ecodev_core/authentication.py
CHANGED
|
@@ -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,
|
ecodev_core/db_upsertion.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Module handling CRUD and version operations
|
|
3
3
|
"""
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
from enum import EnumType
|
|
5
6
|
from functools import partial
|
|
6
7
|
from typing import Any
|
|
7
8
|
from typing import Union
|
|
@@ -14,6 +15,7 @@ from sqlmodel import inspect
|
|
|
14
15
|
from sqlmodel import select
|
|
15
16
|
from sqlmodel import Session
|
|
16
17
|
from sqlmodel import SQLModel
|
|
18
|
+
from sqlmodel import text
|
|
17
19
|
from sqlmodel import update
|
|
18
20
|
from sqlmodel.main import SQLModelMetaclass
|
|
19
21
|
from sqlmodel.sql.expression import SelectOfScalar
|
|
@@ -27,6 +29,33 @@ INFO = 'info'
|
|
|
27
29
|
SA_COLUMN_KWARGS = 'sa_column_kwargs'
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
def add_missing_enum_values(enum: EnumType, session: Session, new_vals: list | None = None) -> None:
|
|
33
|
+
"""
|
|
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.
|
|
36
|
+
|
|
37
|
+
NB: new_val argument is there for testing purposes
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
for val in [e.name for e in new_vals or enum if e.name not in get_enum_values(enum, session)]:
|
|
41
|
+
session.execute(text(f"ALTER TYPE {enum.__name__.lower()} ADD VALUE IF NOT EXISTS '{val}'"))
|
|
42
|
+
session.commit()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_enum_values(enum: EnumType, session: Session) -> set[str]:
|
|
46
|
+
"""
|
|
47
|
+
Return all enum values in db for the passed enum.
|
|
48
|
+
"""
|
|
49
|
+
result = session.execute(text(
|
|
50
|
+
"""
|
|
51
|
+
SELECT enumlabel FROM pg_enum
|
|
52
|
+
JOIN pg_type ON pg_enum.enumtypid = pg_type.oid
|
|
53
|
+
WHERE pg_type.typname = :enum_name
|
|
54
|
+
"""
|
|
55
|
+
), {'enum_name': enum.__name__.lower()})
|
|
56
|
+
return {x[0] for x in result}
|
|
57
|
+
|
|
58
|
+
|
|
30
59
|
def sfield(**kwargs):
|
|
31
60
|
"""
|
|
32
61
|
Field constructor for columns not to be versioned. Those are the columns on which to select.
|
|
@@ -147,11 +176,11 @@ def get_sfield_columns(db_model: SQLModelMetaclass) -> list[str]:
|
|
|
147
176
|
for x in inspect(db_model).c
|
|
148
177
|
if x.info.get(FILTER_ON) is True
|
|
149
178
|
]
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def filter_to_sfield_dict(row: dict | SQLModelMetaclass,
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def filter_to_sfield_dict(row: dict | SQLModelMetaclass,
|
|
182
|
+
db_schema: SQLModelMetaclass | None = None) \
|
|
183
|
+
-> dict[str, dict | SQLModelMetaclass]:
|
|
155
184
|
"""
|
|
156
185
|
Returns a dict with only sfields from object
|
|
157
186
|
Args:
|
|
@@ -162,4 +191,3 @@ def filter_to_sfield_dict(row: dict | SQLModelMetaclass,
|
|
|
162
191
|
"""
|
|
163
192
|
return {pk: getattr(row, pk)
|
|
164
193
|
for pk in get_sfield_columns(db_schema or row.__class__)}
|
|
165
|
-
|
|
@@ -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
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ecodev-core
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.54
|
|
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.
|
|
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)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
ecodev_core/__init__.py,sha256=
|
|
1
|
+
ecodev_core/__init__.py,sha256=xS1h7Xtp-LBGeKkxFfJFqyxeBiznow7c1MPNAeYLtOg,6218
|
|
2
2
|
ecodev_core/app_activity.py,sha256=KBtI-35LBLPDppFB7xjxWthXQrY3Z_aGDnC-HrW8Ea0,4641
|
|
3
3
|
ecodev_core/app_rights.py,sha256=RZPdDtydFqc_nFj96huKAc56BS0qS6ScKv4Kghqd6lc,726
|
|
4
4
|
ecodev_core/app_user.py,sha256=r1bqA4H08x53XmxmjwyGKl_PFjYQazzBbVErdkztqeE,2947
|
|
5
5
|
ecodev_core/auth_configuration.py,sha256=qZ1Dkk7n1AH7w0tVKQ8AYswukOeMZH6mmbixPEAQnJ8,764
|
|
6
|
-
ecodev_core/authentication.py,sha256=
|
|
6
|
+
ecodev_core/authentication.py,sha256=HuA8o_A_jjnTzG6NKqIeh5LP1tb0yyCR1RvXcPy2Xks,11559
|
|
7
7
|
ecodev_core/backup.py,sha256=N5AtoqtHJRp92Bj0Nr7WW5WDcpjTIET8haxZoYDOtyI,3890
|
|
8
8
|
ecodev_core/check_dependencies.py,sha256=aFn8GI4eBbuJT8RxsfhSSnlpNYYj_LPOH-tZF0EqfKQ,6917
|
|
9
9
|
ecodev_core/custom_equal.py,sha256=2gRn0qpyJ8-Kw9GQSueu0nLngLrRrwyMPlP6zqPac0U,899
|
|
@@ -11,7 +11,7 @@ ecodev_core/db_connection.py,sha256=hhqeyTrl0DlQA7RkUs6pIOpZeE3yS_Q5mqj5uGPfG_Y,
|
|
|
11
11
|
ecodev_core/db_filters.py,sha256=T_5JVF27UEu7sC6NOm7-W3_Y0GLfbWQO_EeTXcD2cv8,5041
|
|
12
12
|
ecodev_core/db_insertion.py,sha256=k-r798MMrW1sRb-gb8lQTxyJrb4QP5iZT8GDzCYYwlo,4544
|
|
13
13
|
ecodev_core/db_retrieval.py,sha256=sCP7TDGIcTOK5gT3Inga91bE4S31HbQPw4yI22WJbss,7392
|
|
14
|
-
ecodev_core/db_upsertion.py,sha256=
|
|
14
|
+
ecodev_core/db_upsertion.py,sha256=Nri5innUEcUBc5zFWDq9oqyzC5sjkX7xQHwqriZ6OUI,6814
|
|
15
15
|
ecodev_core/deployment.py,sha256=z8ACI00EtKknXOB8xyPwYIXTvPjIDOH9z9cBGEU0YrA,281
|
|
16
16
|
ecodev_core/email_sender.py,sha256=V3UGweuq6Iy09Z9to8HzM6JOVDVGHZXHGjUSkW94Tac,1912
|
|
17
17
|
ecodev_core/enum_utils.py,sha256=BkQ4YQ97tXBYmMcQiSIi0mbioD5CgVU79myg1BBAXuA,556
|
|
@@ -25,8 +25,9 @@ ecodev_core/read_write.py,sha256=YIGRERvFHU7vy-JIaCWAza4CPMysLRUHKJxN-ZgFmu0,120
|
|
|
25
25
|
ecodev_core/safe_utils.py,sha256=Q8N15El1tSxZJJsy1i_1CCycuBN1_98QQoHmYJRcLIY,6904
|
|
26
26
|
ecodev_core/settings.py,sha256=UvaTv8S_HvfFAL-m1Rfqv_geSGcccuV3ziR1o1d5wu4,1795
|
|
27
27
|
ecodev_core/sqlmodel_utils.py,sha256=t57H3QPtKRy4ujic1clMK_2L4p0yjGJLZbDjHPZ8M94,453
|
|
28
|
+
ecodev_core/token_banlist.py,sha256=rKXG9QkfCpjOTr8gBgdX-KYNHAkKvQ9TRnGS99VC9Co,491
|
|
28
29
|
ecodev_core/version.py,sha256=eyIf8KkW_t-hMuYFIoy0cUlNaMewLe6i45m2HKZKh0Q,4403
|
|
29
|
-
ecodev_core-0.0.
|
|
30
|
-
ecodev_core-0.0.
|
|
31
|
-
ecodev_core-0.0.
|
|
32
|
-
ecodev_core-0.0.
|
|
30
|
+
ecodev_core-0.0.54.dist-info/LICENSE.md,sha256=8dqVJEbwXjPWjjRKjdLMym5k9Gi8hwtrHh84sti6KIs,1068
|
|
31
|
+
ecodev_core-0.0.54.dist-info/METADATA,sha256=B7yPgikOYilTBVwdJs9VXdrdzONDc5JFXPX7cRoyYZI,3510
|
|
32
|
+
ecodev_core-0.0.54.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
33
|
+
ecodev_core-0.0.54.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|