dara-core 1.23.0a1__py3-none-any.whl → 1.23.1__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/_assets/auto_js/dara.core.umd.cjs +2 -0
- dara/core/auth/definitions.py +4 -0
- dara/core/auth/oidc/config.py +80 -17
- dara/core/auth/oidc/routes.py +6 -1
- dara/core/auth/oidc/settings.py +2 -0
- {dara_core-1.23.0a1.dist-info → dara_core-1.23.1.dist-info}/METADATA +10 -10
- {dara_core-1.23.0a1.dist-info → dara_core-1.23.1.dist-info}/RECORD +10 -10
- {dara_core-1.23.0a1.dist-info → dara_core-1.23.1.dist-info}/LICENSE +0 -0
- {dara_core-1.23.0a1.dist-info → dara_core-1.23.1.dist-info}/WHEEL +0 -0
- {dara_core-1.23.0a1.dist-info → dara_core-1.23.1.dist-info}/entry_points.txt +0 -0
|
@@ -27789,8 +27789,10 @@
|
|
|
27789
27789
|
`;
|
|
27790
27790
|
styled.div`
|
|
27791
27791
|
display: flex;
|
|
27792
|
+
align-items: center;
|
|
27792
27793
|
gap: 0.5rem;
|
|
27793
27794
|
width: 100%;
|
|
27795
|
+
line-height: 1;
|
|
27794
27796
|
`;
|
|
27795
27797
|
styled.div`
|
|
27796
27798
|
color: ${(props) => props.theme.colors.grey3};
|
dara/core/auth/definitions.py
CHANGED
|
@@ -18,6 +18,7 @@ limitations under the License.
|
|
|
18
18
|
from contextvars import ContextVar
|
|
19
19
|
from datetime import datetime
|
|
20
20
|
|
|
21
|
+
from pydantic import ConfigDict
|
|
21
22
|
from typing_extensions import TypedDict
|
|
22
23
|
|
|
23
24
|
from dara.core.base_definitions import DaraBaseModel as BaseModel
|
|
@@ -60,6 +61,9 @@ class UserData(BaseModel):
|
|
|
60
61
|
identity_email: str | None = None
|
|
61
62
|
groups: list[str] | None = []
|
|
62
63
|
|
|
64
|
+
# allow extra for more flexibility in custom oidc configs
|
|
65
|
+
model_config = ConfigDict(extra='allow')
|
|
66
|
+
|
|
63
67
|
@classmethod
|
|
64
68
|
def from_token_data(cls, token_data: TokenData):
|
|
65
69
|
return cls(
|
dara/core/auth/oidc/config.py
CHANGED
|
@@ -65,6 +65,7 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
65
65
|
- SSO_EXTRA_AUDIENCE - if set, extra audiences to verify against the ID token in addition to `sso_client_id`
|
|
66
66
|
- SSO_SCOPES - space-separated list of scopes to request from the identity provider, defaults to `openid`
|
|
67
67
|
- SSO_JWT_ALGO - algorithm to use for verifying IDP-provided JWTs, defaults to `ES256`
|
|
68
|
+
- SSO_USE_USERINFO - if set to `true`, fetch additional claims from the userinfo endpoint when an access token is available
|
|
68
69
|
"""
|
|
69
70
|
|
|
70
71
|
# NOTE: the config follows OIDC specification, but makes a few concessions
|
|
@@ -209,29 +210,84 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
209
210
|
state = self.generate_state(redirect_to=body.redirect_to)
|
|
210
211
|
return RedirectResponse(redirect_uri=self.get_authorization_url(state))
|
|
211
212
|
|
|
212
|
-
def
|
|
213
|
+
async def fetch_userinfo(self, access_token: str) -> dict | None:
|
|
213
214
|
"""
|
|
214
|
-
|
|
215
|
+
Fetch user information from the OIDC userinfo endpoint.
|
|
216
|
+
|
|
217
|
+
Per OpenID Connect Core 1.0 Section 5.3, the userinfo endpoint returns claims
|
|
218
|
+
about the authenticated user. This is useful when the ID token doesn't contain
|
|
219
|
+
all required claims.
|
|
220
|
+
|
|
221
|
+
:param access_token: The access token to authenticate the request
|
|
222
|
+
:return: Dictionary of userinfo claims, or None if the request fails
|
|
223
|
+
"""
|
|
224
|
+
userinfo_endpoint = self.discovery.userinfo_endpoint
|
|
225
|
+
if not userinfo_endpoint:
|
|
226
|
+
dev_logger.warning('Userinfo endpoint not available in OIDC discovery')
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
response = await self.client.get(
|
|
231
|
+
userinfo_endpoint,
|
|
232
|
+
headers={'Authorization': f'Bearer {access_token}'},
|
|
233
|
+
timeout=10,
|
|
234
|
+
)
|
|
235
|
+
response.raise_for_status()
|
|
236
|
+
return response.json()
|
|
237
|
+
except httpx.HTTPStatusError as e:
|
|
238
|
+
dev_logger.warning(
|
|
239
|
+
f'Failed to fetch userinfo: HTTP {e.response.status_code}',
|
|
240
|
+
)
|
|
241
|
+
return None
|
|
242
|
+
except httpx.RequestError as e:
|
|
243
|
+
dev_logger.warning(f'Failed to fetch userinfo: {e}')
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def extract_user_data(self, claims: IdTokenClaims, userinfo: dict | None = None) -> UserData:
|
|
247
|
+
"""
|
|
248
|
+
Extract user data from ID token claims and optional userinfo response.
|
|
215
249
|
|
|
216
250
|
Override this method in subclasses to handle provider-specific claim structures.
|
|
217
251
|
The default implementation uses standard OIDC claims, with support for the
|
|
218
|
-
non-standard 'identity' claim.
|
|
252
|
+
non-standard 'identity' claim. When userinfo is provided and SSO_USE_USERINFO
|
|
253
|
+
is enabled, userinfo claims take precedence over ID token claims.
|
|
219
254
|
|
|
220
255
|
:param claims: Decoded ID token claims
|
|
256
|
+
:param userinfo: Optional userinfo response from the userinfo endpoint
|
|
221
257
|
:return: UserData extracted from the claims
|
|
222
258
|
"""
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
identity_email =
|
|
259
|
+
oidc_settings = get_oidc_settings()
|
|
260
|
+
|
|
261
|
+
# When userinfo is provided and use_userinfo is enabled, prefer userinfo claims
|
|
262
|
+
if userinfo and oidc_settings.use_userinfo:
|
|
263
|
+
# userinfo 'sub' must match id_token 'sub' per OIDC spec
|
|
264
|
+
identity_id = userinfo.get('sub') or claims.sub
|
|
265
|
+
identity_email = userinfo.get('email') or claims.email
|
|
266
|
+
identity_name = (
|
|
267
|
+
userinfo.get('name')
|
|
268
|
+
or userinfo.get('preferred_username')
|
|
269
|
+
or userinfo.get('nickname')
|
|
270
|
+
or (
|
|
271
|
+
f'{userinfo.get("given_name", "")} {userinfo.get("family_name", "")}'.strip()
|
|
272
|
+
if userinfo.get('given_name') or userinfo.get('family_name')
|
|
273
|
+
else None
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
groups = userinfo.get('groups') or claims.groups
|
|
230
277
|
else:
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
278
|
+
# Check for non-standard 'identity' claim (Causalens IDP)
|
|
279
|
+
# This is a nested object with id, name, email fields
|
|
280
|
+
identity_claim = getattr(claims, 'identity', None)
|
|
281
|
+
if isinstance(identity_claim, dict):
|
|
282
|
+
identity_id = identity_claim.get('id') or claims.sub
|
|
283
|
+
identity_name = identity_claim.get('name')
|
|
284
|
+
identity_email = identity_claim.get('email') or claims.email
|
|
285
|
+
else:
|
|
286
|
+
# Standard OIDC: use 'sub' as the identity ID
|
|
287
|
+
identity_id = claims.sub
|
|
288
|
+
identity_email = claims.email
|
|
289
|
+
identity_name = None
|
|
290
|
+
groups = claims.groups
|
|
235
291
|
|
|
236
292
|
# Fall back to standard claims for name if not set
|
|
237
293
|
if not identity_name:
|
|
@@ -252,7 +308,7 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
252
308
|
identity_id=identity_id,
|
|
253
309
|
identity_name=identity_name,
|
|
254
310
|
identity_email=identity_email,
|
|
255
|
-
groups=
|
|
311
|
+
groups=groups,
|
|
256
312
|
)
|
|
257
313
|
|
|
258
314
|
def verify_token(self, token: str) -> TokenData:
|
|
@@ -291,7 +347,7 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
291
347
|
claims = decode_id_token(token)
|
|
292
348
|
|
|
293
349
|
# Extract user data (can be overridden for provider-specific claim structures)
|
|
294
|
-
user_data = self.
|
|
350
|
+
user_data = self.extract_user_data(claims)
|
|
295
351
|
|
|
296
352
|
# Verify user has access based on groups
|
|
297
353
|
self.verify_user_access(user_data)
|
|
@@ -390,6 +446,8 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
390
446
|
:return: Tuple of (new_session_token, new_refresh_token)
|
|
391
447
|
:raises HTTPException: If the refresh fails
|
|
392
448
|
"""
|
|
449
|
+
oidc_settings = get_oidc_settings()
|
|
450
|
+
|
|
393
451
|
# Request new tokens from the IDP
|
|
394
452
|
oidc_tokens = await get_token_from_idp(
|
|
395
453
|
self,
|
|
@@ -406,8 +464,13 @@ class OIDCAuthConfig(BaseAuthConfig):
|
|
|
406
464
|
# Decode and verify the new ID token
|
|
407
465
|
claims = decode_id_token(oidc_tokens.id_token)
|
|
408
466
|
|
|
467
|
+
# Fetch userinfo if enabled and we have an access token
|
|
468
|
+
userinfo = None
|
|
469
|
+
if oidc_settings.use_userinfo and oidc_tokens.access_token:
|
|
470
|
+
userinfo = await self.fetch_userinfo(oidc_tokens.access_token)
|
|
471
|
+
|
|
409
472
|
# Extract user data from claims
|
|
410
|
-
user_data = self.
|
|
473
|
+
user_data = self.extract_user_data(claims, userinfo=userinfo)
|
|
411
474
|
|
|
412
475
|
# Verify user still has access
|
|
413
476
|
self.verify_user_access(user_data)
|
dara/core/auth/oidc/routes.py
CHANGED
|
@@ -100,8 +100,13 @@ async def sso_callback(
|
|
|
100
100
|
# Decode and verify the ID token
|
|
101
101
|
claims = decode_id_token(oidc_tokens.id_token)
|
|
102
102
|
|
|
103
|
+
# Fetch userinfo if enabled and we have an access token
|
|
104
|
+
userinfo = None
|
|
105
|
+
if oidc_settings.use_userinfo and oidc_tokens.access_token:
|
|
106
|
+
userinfo = await auth_config.fetch_userinfo(oidc_tokens.access_token)
|
|
107
|
+
|
|
103
108
|
# Extract user data from claims (handles both standard OIDC and Causalens identity claim)
|
|
104
|
-
user_data = auth_config.
|
|
109
|
+
user_data = auth_config.extract_user_data(claims, userinfo=userinfo)
|
|
105
110
|
|
|
106
111
|
# Verify user has access based on groups
|
|
107
112
|
auth_config.verify_user_access(user_data)
|
dara/core/auth/oidc/settings.py
CHANGED
|
@@ -24,6 +24,8 @@ class OIDCSettings(BaseSettings):
|
|
|
24
24
|
verify_audience: bool = False
|
|
25
25
|
extra_audience: list[str] | None = None
|
|
26
26
|
allowed_identity_id: str | None = None
|
|
27
|
+
use_userinfo: bool = False
|
|
28
|
+
"""If True, fetch additional claims from the userinfo endpoint when an access token is available."""
|
|
27
29
|
|
|
28
30
|
model_config = SettingsConfigDict(env_file='.env', extra='allow', env_prefix='sso_')
|
|
29
31
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dara-core
|
|
3
|
-
Version: 1.23.
|
|
3
|
+
Version: 1.23.1
|
|
4
4
|
Summary: Dara Framework Core
|
|
5
5
|
Home-page: https://dara.causalens.com/
|
|
6
6
|
License: Apache-2.0
|
|
@@ -21,10 +21,10 @@ Requires-Dist: cachetools (>=5.0.0)
|
|
|
21
21
|
Requires-Dist: certifi (>=2024.7.4)
|
|
22
22
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
23
23
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
24
|
-
Requires-Dist: create-dara-app (==1.23.
|
|
24
|
+
Requires-Dist: create-dara-app (==1.23.1)
|
|
25
25
|
Requires-Dist: croniter (>=6.0.0,<7.0.0)
|
|
26
26
|
Requires-Dist: cryptography (>=42.0.4)
|
|
27
|
-
Requires-Dist: dara-components (==1.23.
|
|
27
|
+
Requires-Dist: dara-components (==1.23.1) ; extra == "all"
|
|
28
28
|
Requires-Dist: exceptiongroup (>=1.1.3,<2.0.0)
|
|
29
29
|
Requires-Dist: fastapi (>=0.115.0,<0.121.0)
|
|
30
30
|
Requires-Dist: fastapi_vite_dara (==0.4.0)
|
|
@@ -55,7 +55,7 @@ Description-Content-Type: text/markdown
|
|
|
55
55
|
|
|
56
56
|
# Dara Application Framework
|
|
57
57
|
|
|
58
|
-
<img src="https://github.com/causalens/dara/blob/v1.23.
|
|
58
|
+
<img src="https://github.com/causalens/dara/blob/v1.23.1/img/dara_light.svg?raw=true">
|
|
59
59
|
|
|
60
60
|

|
|
61
61
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
@@ -100,7 +100,7 @@ source .venv/bin/activate
|
|
|
100
100
|
dara start
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-

|
|
104
104
|
|
|
105
105
|
Note: `pip` installation uses [PEP 660](https://peps.python.org/pep-0660/) `pyproject.toml`-based editable installs which require `pip >= 21.3` and `setuptools >= 64.0.0`. You can upgrade both with:
|
|
106
106
|
|
|
@@ -117,9 +117,9 @@ Explore some of our favorite apps - a great way of getting started and getting t
|
|
|
117
117
|
|
|
118
118
|
| Dara App | Description |
|
|
119
119
|
| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
120
|
-
|  | Demonstrates how to use incorporate a LLM chat box into your decision app to understand model insights |
|
|
121
|
+
|  | Demonstrates how to enable the user to interact with plots, trigger actions based on clicks, mouse movements and other interactions with `Bokeh` or `Plotly` plots |
|
|
122
|
+
|  | Demonstrates how to use the `CausalGraphViewer` component to display your graphs or networks, customising the displayed information through colors and tooltips, and updating the page based on user interaction. |
|
|
123
123
|
|
|
124
124
|
Check out our [App Gallery](https://dara.causalens.com/gallery) for more inspiration!
|
|
125
125
|
|
|
@@ -146,9 +146,9 @@ And the supporting UI packages and tools.
|
|
|
146
146
|
- `ui-utils` - miscellaneous utility functions
|
|
147
147
|
- `ui-widgets` - widget components
|
|
148
148
|
|
|
149
|
-
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.23.
|
|
149
|
+
More information on the repository structure can be found in the [CONTRIBUTING.md](https://github.com/causalens/dara/blob/v1.23.1/CONTRIBUTING.md) file.
|
|
150
150
|
|
|
151
151
|
## License
|
|
152
152
|
|
|
153
|
-
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.23.
|
|
153
|
+
Dara is open-source and licensed under the [Apache 2.0 License](https://github.com/causalens/dara/blob/v1.23.1/LICENSE).
|
|
154
154
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
dara/core/__init__.py,sha256=yTp-lXT0yy9XqLGYWlmjPgFG5g2eEg2KhKo8KheTHoo,1408
|
|
2
2
|
dara/core/_assets/__init__.py,sha256=13vMoWHvl1zcFcjNHh8lbTwWOvu4f7krYSco978qxwM,723
|
|
3
3
|
dara/core/_assets/auto_js/dara.core.css,sha256=yT3PKpi2sKI2-kQIF8xtVbTPQqgpK7-Ua7tfzDPuSsI,4095881
|
|
4
|
-
dara/core/_assets/auto_js/dara.core.umd.cjs,sha256=
|
|
4
|
+
dara/core/_assets/auto_js/dara.core.umd.cjs,sha256=hvmFXD-p5KiQFZdoyp6z-Js_DG6r2bFIzXKoGtj5QFQ,5163770
|
|
5
5
|
dara/core/_assets/auto_js/react-dom.development.js,sha256=vR2Fq5LXMKS5JsTo2CMl6oGCJT8scJV2wXSteTtx8aE,1077040
|
|
6
6
|
dara/core/_assets/auto_js/react-is.development.js,sha256=2IRgmaphdMq6wx2MbsmVUQ0UwapGETNjgano3XEkGcc,7932
|
|
7
7
|
dara/core/_assets/auto_js/react-query.development.js,sha256=lI2fTKMvWmjbagGQaVtZYr51_-C_U_so064JwetuDS0,130366
|
|
@@ -12,12 +12,12 @@ dara/core/actions.py,sha256=rC5Tu79AFNWMv0CJuchBnoy6pETIFh_1RTSqxrolArI,947
|
|
|
12
12
|
dara/core/auth/__init__.py,sha256=HJoYIVPzbpwzN_RUHjGpSJj4o5TmHz9yFyZGiRiObCk,906
|
|
13
13
|
dara/core/auth/base.py,sha256=NJmUJqA-W8AVKIQbX_0BoHoZqtU1Iz6cJ16RKdcdIyU,3642
|
|
14
14
|
dara/core/auth/basic.py,sha256=sglIaogCslG2HlDMjFsaaJhOJeXUW-QQLTIYPaUPxAU,4927
|
|
15
|
-
dara/core/auth/definitions.py,sha256=
|
|
15
|
+
dara/core/auth/definitions.py,sha256=ZZMXJbMFY42Q5YaJCMwZT0bivnxq7TaIoug7uYqAGzE,3988
|
|
16
16
|
dara/core/auth/oidc/__init__.py,sha256=UWdhFvDqLCoILaKVWbmrrJgiMgg9wlVZgCxRvf_HGHM,65
|
|
17
|
-
dara/core/auth/oidc/config.py,sha256=
|
|
17
|
+
dara/core/auth/oidc/config.py,sha256=XdiJSC2-9g8fqM-FkmU0c079VqnrApVv-MKwEBwk50s,23753
|
|
18
18
|
dara/core/auth/oidc/definitions.py,sha256=KLlUl2Y7n6prX0JSAwPde6J43iMs6uUY07OG7kumRPw,17568
|
|
19
|
-
dara/core/auth/oidc/routes.py,sha256=
|
|
20
|
-
dara/core/auth/oidc/settings.py,sha256=
|
|
19
|
+
dara/core/auth/oidc/routes.py,sha256=wlYp8VZP4C4BFHqJPPYHi3PdLd8SetdG2CtG3B4laBk,5579
|
|
20
|
+
dara/core/auth/oidc/settings.py,sha256=wU6fmkqhTNzMNFUxizFaaavH2cKuXeqRIzMi3TpwLhA,2233
|
|
21
21
|
dara/core/auth/oidc/utils.py,sha256=IL17tStRNpkAQI3M17nrzGeb4xr47zSLPo4wT7LMwBQ,5145
|
|
22
22
|
dara/core/auth/routes.py,sha256=dtOxpFotnt4XQ4spW3mbyM7ThYRvfIA_oRK5X5lyYHg,7256
|
|
23
23
|
dara/core/auth/utils.py,sha256=_aHZ98qMJ0VLE9Zfvj5biPARhe973_eyTx2qxnufzRE,7290
|
|
@@ -134,8 +134,8 @@ dara/core/visual/themes/__init__.py,sha256=aM4mgoIYo2neBSw5FRzswsht7PUKjLthiHLmF
|
|
|
134
134
|
dara/core/visual/themes/dark.py,sha256=QazCRDqh_SCOyQhdwMkH1wbHf301oL7gCFj91plbLww,2020
|
|
135
135
|
dara/core/visual/themes/definitions.py,sha256=dtET2YUlwXkO6gJ23MqSb8gIq-LxJ343CWsgueWSifM,2787
|
|
136
136
|
dara/core/visual/themes/light.py,sha256=dtHb6Q1HOb5r_AvJfe0vZajikVc-GnBEUrGsTcI5MHA,2022
|
|
137
|
-
dara_core-1.23.
|
|
138
|
-
dara_core-1.23.
|
|
139
|
-
dara_core-1.23.
|
|
140
|
-
dara_core-1.23.
|
|
141
|
-
dara_core-1.23.
|
|
137
|
+
dara_core-1.23.1.dist-info/LICENSE,sha256=r9u1w2RvpLMV6YjuXHIKXRBKzia3fx_roPwboGcLqCc,10944
|
|
138
|
+
dara_core-1.23.1.dist-info/METADATA,sha256=gVUjEVltqKHlaPr_tu3FypSAhpceR2M1-HJCwP8cT84,7524
|
|
139
|
+
dara_core-1.23.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
140
|
+
dara_core-1.23.1.dist-info/entry_points.txt,sha256=nAT9o1kJCmTK1saDh29PFGFD6cbxDDDjTj31HDEDwfU,197
|
|
141
|
+
dara_core-1.23.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|