|
17 | 17 | import secrets |
18 | 18 | import sys |
19 | 19 | import shutil |
| 20 | +from authlib.jose import jwt, JoseError |
| 21 | +from authlib.jose.rfc7517.jwk import JsonWebKey |
20 | 22 | from typing import Any, Dict, List, Optional, Union, Generator |
21 | 23 | from uuid import UUID, uuid4 |
22 | 24 |
|
@@ -433,6 +435,13 @@ def _get_user_from_invenio_user(id): |
433 | 435 | return user |
434 | 436 |
|
435 | 437 |
|
| 438 | +def _get_user_by_idpid(idp_id): |
| 439 | + user = Session.query(User).filter_by(idp_id=idp_id).one_or_none() |
| 440 | + if not user: |
| 441 | + raise ValueError("No users registered with this id") |
| 442 | + return user |
| 443 | + |
| 444 | + |
436 | 445 | def _get_reana_yaml_from_gitlab(webhook_data, user_id): |
437 | 446 | reana_yaml = "reana.yaml" |
438 | 447 | if webhook_data["object_kind"] == "push": |
@@ -658,3 +667,61 @@ def render_template(template_path, **kwargs): |
658 | 667 | """Render template replacing kwargs appropriately.""" |
659 | 668 | template = JinjaEnv._get().get_template(template_path) |
660 | 669 | return template.render(**kwargs) |
| 670 | + |
| 671 | + |
| 672 | +def fetch_and_parse_jwk(): |
| 673 | + """Fetch and return specific JWK from an identity provider. |
| 674 | +
|
| 675 | + Returns: |
| 676 | + dict: JWK matching the kid |
| 677 | +
|
| 678 | + Raises: |
| 679 | + ValueError: If JWK fetch fails or no matching key found |
| 680 | + """ |
| 681 | + if not hasattr(fetch_and_parse_jwk, "_cache"): |
| 682 | + fetch_and_parse_jwk._cache = None |
| 683 | + |
| 684 | + if not fetch_and_parse_jwk._cache: |
| 685 | + response = requests.get("https://iam-escape.cloud.cnaf.infn.it/jwk") |
| 686 | + if response.status_code != 200: |
| 687 | + raise ValueError(f"Failed to fetch JWK: {response.status_code}") |
| 688 | + fetch_and_parse_jwk._cache = response.json() |
| 689 | + jwks = fetch_and_parse_jwk._cache.get("keys", []) |
| 690 | + if not jwks: |
| 691 | + raise ValueError("No JWKs found in the response") |
| 692 | + return jwks |
| 693 | + |
| 694 | + |
| 695 | +def _get_user_from_jwt(header: str) -> User: |
| 696 | + """Get user from JWT token. |
| 697 | +
|
| 698 | + Args: |
| 699 | + header: JWT Authorization header in the format "Bearer <token>" |
| 700 | +
|
| 701 | + Returns: |
| 702 | + User: REANA user if token is valid |
| 703 | +
|
| 704 | + Raises: |
| 705 | + ValueError: If token is invalid or user not found |
| 706 | + """ |
| 707 | + try: |
| 708 | + if not header.startswith("Bearer "): |
| 709 | + raise ValueError("Invalid authorization header format") |
| 710 | + |
| 711 | + token = header.split(" ")[1] |
| 712 | + |
| 713 | + jwks = fetch_and_parse_jwk() |
| 714 | + key_set = JsonWebKey.import_key_set(jwks) |
| 715 | + |
| 716 | + claims = jwt.decode(token, key_set) |
| 717 | + claims.validate() |
| 718 | + |
| 719 | + idp_id = claims.get("sub") |
| 720 | + if not idp_id: |
| 721 | + raise ValueError("Token missing subject claim") |
| 722 | + |
| 723 | + return _get_user_by_idpid(idp_id) |
| 724 | + except JoseError as e: |
| 725 | + raise ValueError(f"Invalid token: {str(e)}") |
| 726 | + except Exception as e: |
| 727 | + raise ValueError(f"Error processing token: {str(e)}") |
0 commit comments