dara-core 1.13.0__py3-none-any.whl → 1.14.0a0__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.
- dara/core/auth/base.py +10 -1
- dara/core/auth/routes.py +51 -1
- dara/core/interactivity/data_variable.py +7 -0
- dara/core/interactivity/derived_data_variable.py +7 -0
- dara/core/internal/routing.py +2 -0
- dara/core/umd/dara.core.umd.js +722 -778
- {dara_core-1.13.0.dist-info → dara_core-1.14.0a0.dist-info}/METADATA +25 -10
- {dara_core-1.13.0.dist-info → dara_core-1.14.0a0.dist-info}/RECORD +11 -11
- {dara_core-1.13.0.dist-info → dara_core-1.14.0a0.dist-info}/LICENSE +0 -0
- {dara_core-1.13.0.dist-info → dara_core-1.14.0a0.dist-info}/WHEEL +0 -0
- {dara_core-1.13.0.dist-info → dara_core-1.14.0a0.dist-info}/entry_points.txt +0 -0
dara/core/auth/base.py
CHANGED
|
@@ -18,7 +18,7 @@ limitations under the License.
|
|
|
18
18
|
import abc
|
|
19
19
|
from typing import Any, ClassVar, Dict, Union
|
|
20
20
|
|
|
21
|
-
from fastapi import Response
|
|
21
|
+
from fastapi import HTTPException, Response
|
|
22
22
|
from pydantic import BaseModel
|
|
23
23
|
from typing_extensions import TypedDict
|
|
24
24
|
|
|
@@ -91,6 +91,15 @@ class BaseAuthConfig(BaseModel, abc.ABC):
|
|
|
91
91
|
:param token: encoded token
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
|
+
def refresh_token(self, refresh_token: str) -> tuple[str, str]:
|
|
95
|
+
"""
|
|
96
|
+
Create a new session token and refresh token from a refresh token.
|
|
97
|
+
|
|
98
|
+
:param refresh_token: encoded refresh token
|
|
99
|
+
:return: new session token, new refresh token
|
|
100
|
+
"""
|
|
101
|
+
raise HTTPException(400, f'Auth config {self.__class__.__name__} does not support token refresh')
|
|
102
|
+
|
|
94
103
|
def revoke_token(self, token: str, response: Response) -> Union[SuccessResponse, RedirectResponse]:
|
|
95
104
|
"""
|
|
96
105
|
Revoke a session token.
|
dara/core/auth/routes.py
CHANGED
|
@@ -16,9 +16,10 @@ limitations under the License.
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
from inspect import iscoroutinefunction
|
|
19
|
+
from typing import Union, cast
|
|
19
20
|
|
|
20
21
|
import jwt
|
|
21
|
-
from fastapi import APIRouter, Depends, HTTPException, Request, Response
|
|
22
|
+
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, Response
|
|
22
23
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
23
24
|
|
|
24
25
|
from dara.core.auth.base import BaseAuthConfig
|
|
@@ -103,6 +104,55 @@ async def _revoke_session(response: Response, credentials: HTTPAuthorizationCred
|
|
|
103
104
|
raise HTTPException(status_code=400, detail=BAD_REQUEST_ERROR('No auth credentials passed'))
|
|
104
105
|
|
|
105
106
|
|
|
107
|
+
@auth_router.post('/refresh-token')
|
|
108
|
+
async def handle_refresh_token(
|
|
109
|
+
response: Response,
|
|
110
|
+
dara_refresh_token: Union[str, None] = Cookie(default=None),
|
|
111
|
+
):
|
|
112
|
+
"""
|
|
113
|
+
Given a refresh token, issues a new session token and refresh token cookie.
|
|
114
|
+
|
|
115
|
+
:param response: FastAPI response object
|
|
116
|
+
:param dara_refresh_token: refresh token cookie
|
|
117
|
+
:param settings: env settings object
|
|
118
|
+
"""
|
|
119
|
+
if dara_refresh_token is None:
|
|
120
|
+
raise HTTPException(status_code=400, detail=BAD_REQUEST_ERROR('No refresh token provided'))
|
|
121
|
+
|
|
122
|
+
from dara.core.internal.registries import auth_registry
|
|
123
|
+
|
|
124
|
+
auth_config: BaseAuthConfig = auth_registry.get('auth_config')
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Refresh logic up to implementation
|
|
128
|
+
session_token, refresh_token = auth_config.refresh_token(dara_refresh_token)
|
|
129
|
+
|
|
130
|
+
# Using 'Strict' as it is only used for the refresh-token endpoint so cross-site requests are not expected
|
|
131
|
+
response.set_cookie(
|
|
132
|
+
key='dara_refresh_token', value=refresh_token, secure=True, httponly=True, samesite='strict'
|
|
133
|
+
)
|
|
134
|
+
return {'token': session_token}
|
|
135
|
+
except BaseException as e:
|
|
136
|
+
# Regardless of exception type, clear the refresh token cookie
|
|
137
|
+
response.delete_cookie('dara_refresh_token')
|
|
138
|
+
headers = {'set-cookie': response.headers['set-cookie']}
|
|
139
|
+
|
|
140
|
+
# If an explicit HTTPException was raised, re-raise it with the cookie header
|
|
141
|
+
if isinstance(e, HTTPException):
|
|
142
|
+
dev_logger.error('Auth Error', error=e)
|
|
143
|
+
e.headers = headers
|
|
144
|
+
raise e
|
|
145
|
+
|
|
146
|
+
# Explicitly handle expired signature error
|
|
147
|
+
if isinstance(e, jwt.ExpiredSignatureError):
|
|
148
|
+
dev_logger.error('Expired Token Signature', error=e)
|
|
149
|
+
raise HTTPException(status_code=401, detail=EXPIRED_TOKEN_ERROR, headers=headers)
|
|
150
|
+
|
|
151
|
+
# Otherwise show a generic invalid token error
|
|
152
|
+
dev_logger.error('Invalid Token', error=cast(Exception, e))
|
|
153
|
+
raise HTTPException(status_code=401, detail=INVALID_TOKEN_ERROR, headers=headers)
|
|
154
|
+
|
|
155
|
+
|
|
106
156
|
# Request to retrieve a session token from the backend. The app does this on startup.
|
|
107
157
|
@auth_router.post('/session')
|
|
108
158
|
async def _get_session(body: SessionRequestBody):
|
|
@@ -182,6 +182,7 @@ class DataVariable(AnyDataVariable):
|
|
|
182
182
|
store: CacheStore,
|
|
183
183
|
filters: Optional[Union[FilterQuery, dict]] = None,
|
|
184
184
|
pagination: Optional[Pagination] = None,
|
|
185
|
+
format_for_display: bool = False,
|
|
185
186
|
) -> Optional[DataFrame]:
|
|
186
187
|
"""
|
|
187
188
|
Get the value of this DataVariable.
|
|
@@ -212,6 +213,12 @@ class DataVariable(AnyDataVariable):
|
|
|
212
213
|
|
|
213
214
|
if entry.data is not None:
|
|
214
215
|
filtered_data, count = apply_filters(entry.data, coerce_to_filter_query(filters), pagination)
|
|
216
|
+
if format_for_display and filtered_data is not None:
|
|
217
|
+
filtered_data = filtered_data.copy()
|
|
218
|
+
for col in filtered_data.columns:
|
|
219
|
+
if filtered_data[col].dtype == 'object':
|
|
220
|
+
# We need to convert all values to string to avoid issues with displaying data in the Table component, for example when displaying datetime and number objects in the same column
|
|
221
|
+
filtered_data.loc[:, col] = filtered_data[col].apply(str)
|
|
215
222
|
data = filtered_data
|
|
216
223
|
# Store count for given filters and schema
|
|
217
224
|
await asyncio.gather(
|
|
@@ -239,6 +239,7 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
|
|
|
239
239
|
store: CacheStore,
|
|
240
240
|
filters: Optional[Union[FilterQuery, dict]] = None,
|
|
241
241
|
pagination: Optional[Pagination] = None,
|
|
242
|
+
format_for_display: bool = False,
|
|
242
243
|
) -> Union[BaseTask, DataFrame, None]:
|
|
243
244
|
"""
|
|
244
245
|
Get the filtered data from the underlying derived variable stored under the specified cache_key.
|
|
@@ -297,6 +298,12 @@ class DerivedDataVariable(AnyDataVariable, DerivedVariable):
|
|
|
297
298
|
|
|
298
299
|
# Run the filtering
|
|
299
300
|
data = await cls._filter_data(data, count_cache_key, data_entry, store, filters, pagination)
|
|
301
|
+
if format_for_display and data is not None:
|
|
302
|
+
data = data.copy()
|
|
303
|
+
for col in data.columns:
|
|
304
|
+
if data[col].dtype == 'object':
|
|
305
|
+
# We need to convert all values to string to avoid issues with displaying data in the Table component, for example when displaying datetime and number objects in the same column
|
|
306
|
+
data.loc[:, col] = data[col].apply(str)
|
|
300
307
|
|
|
301
308
|
return data
|
|
302
309
|
|
dara/core/internal/routing.py
CHANGED
|
@@ -332,6 +332,7 @@ def create_router(config: Configuration):
|
|
|
332
332
|
store,
|
|
333
333
|
body.filters,
|
|
334
334
|
Pagination(offset=offset, limit=limit, orderBy=order_by, index=index),
|
|
335
|
+
format_for_display=True,
|
|
335
336
|
)
|
|
336
337
|
if isinstance(data, BaseTask):
|
|
337
338
|
await task_mgr.run_task(data, body.ws_channel)
|
|
@@ -342,6 +343,7 @@ def create_router(config: Configuration):
|
|
|
342
343
|
store,
|
|
343
344
|
body.filters,
|
|
344
345
|
Pagination(offset=offset, limit=limit, orderBy=order_by, index=index),
|
|
346
|
+
format_for_display=True,
|
|
345
347
|
)
|
|
346
348
|
|
|
347
349
|
dev_logger.debug(
|