pypomes-jwt 1.3.9__py3-none-any.whl → 1.4.0__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 pypomes-jwt might be problematic. Click here for more details.
- pypomes_jwt/__init__.py +2 -2
- pypomes_jwt/jwt_pomes.py +108 -1
- {pypomes_jwt-1.3.9.dist-info → pypomes_jwt-1.4.0.dist-info}/METADATA +3 -2
- pypomes_jwt-1.4.0.dist-info/RECORD +8 -0
- pypomes_jwt-1.3.9.dist-info/RECORD +0 -8
- {pypomes_jwt-1.3.9.dist-info → pypomes_jwt-1.4.0.dist-info}/WHEEL +0 -0
- {pypomes_jwt-1.3.9.dist-info → pypomes_jwt-1.4.0.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@ from .jwt_config import (
|
|
|
2
2
|
JwtConfig, JwtDbConfig, JwtAlgorithm
|
|
3
3
|
)
|
|
4
4
|
from .jwt_pomes import (
|
|
5
|
-
jwt_needed, jwt_verify_request,
|
|
5
|
+
jwt_needed, jwt_verify_request, jwt_get_public_key,
|
|
6
6
|
jwt_assert_account, jwt_set_account, jwt_remove_account,
|
|
7
7
|
jwt_issue_token, jwt_issue_tokens, jwt_refresh_tokens,
|
|
8
8
|
jwt_get_claims, jwt_validate_token, jwt_revoke_token
|
|
@@ -12,7 +12,7 @@ __all__ = [
|
|
|
12
12
|
# jwt_config
|
|
13
13
|
"JwtConfig", "JwtDbConfig", "JwtAlgorithm",
|
|
14
14
|
# jwt_pomes
|
|
15
|
-
"jwt_needed", "jwt_verify_request",
|
|
15
|
+
"jwt_needed", "jwt_verify_request", "jwt_get_public_key",
|
|
16
16
|
"jwt_assert_account", "jwt_set_account", "jwt_remove_account",
|
|
17
17
|
"jwt_issue_token", "jwt_issue_tokens", "jwt_refresh_tokens",
|
|
18
18
|
"jwt_get_claims", "jwt_validate_token", "jwt_revoke_token"
|
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import jwt
|
|
2
|
+
import requests
|
|
2
3
|
import sys
|
|
3
4
|
from base64 import b64decode
|
|
4
5
|
from flask import Request, Response, request
|
|
@@ -8,7 +9,7 @@ from pypomes_db import (
|
|
|
8
9
|
DbEngine, db_connect, db_commit,
|
|
9
10
|
db_rollback, db_close, db_select, db_delete
|
|
10
11
|
)
|
|
11
|
-
from typing import Any
|
|
12
|
+
from typing import Any, Literal
|
|
12
13
|
|
|
13
14
|
from .jwt_config import JwtConfig, JwtDbConfig
|
|
14
15
|
from .jwt_registry import JwtRegistry
|
|
@@ -143,6 +144,7 @@ def jwt_validate_token(token: str,
|
|
|
143
144
|
then the cryptographic key needed for validation will be obtained from the token database.
|
|
144
145
|
Otherwise, the current decoding key is used.
|
|
145
146
|
|
|
147
|
+
Validation operations require access to a database table defined by *JWT_DB_TABLE*.
|
|
146
148
|
On success, return the token's claims (*header* and *payload*), as documented in *jwt_get_claims()*
|
|
147
149
|
On failure, *errors* will contain the reason(s) for rejecting *token*.
|
|
148
150
|
|
|
@@ -568,3 +570,108 @@ def jwt_get_claims(token: str,
|
|
|
568
570
|
errors.append(exc_err)
|
|
569
571
|
|
|
570
572
|
return result
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def jwt_get_public_key(token: str,
|
|
576
|
+
fmt: Literal["DER", "PEM"] = None,
|
|
577
|
+
errors: list[str] = None,
|
|
578
|
+
logger: Logger = None) -> dict[str, str] | str | None:
|
|
579
|
+
"""
|
|
580
|
+
Obtain the public key used *token*.
|
|
581
|
+
|
|
582
|
+
This is accomplished by requesting the token issuer for its *JWKS* (JSON Web Key Set),
|
|
583
|
+
containing the public keys used for various purposes, as indicated in the attribute *use*:
|
|
584
|
+
- *enc*: the key is intended for encryption
|
|
585
|
+
- *sig*: the key is intended for digital signature
|
|
586
|
+
- *wrap*: the key is intended for key wrapping
|
|
587
|
+
|
|
588
|
+
A typical JWKS set has the following format (for simplicity, 'n' and 'x5c' are truncated):
|
|
589
|
+
{
|
|
590
|
+
"keys": [
|
|
591
|
+
{
|
|
592
|
+
"kid": "X2QEcSQ4Tg2M2EK6s2nhRHZH_GwD_zxZtiWVwP4S0tg",
|
|
593
|
+
"kty": "RSA",
|
|
594
|
+
"alg": "RSA256",
|
|
595
|
+
"use": "sig",
|
|
596
|
+
"n": "tQmDmyM3tMFt5FMVMbqbQYpaDPf6A5l4e_kTVDBiHrK_bRlGfkk8hYm5SNzNzCZ...",
|
|
597
|
+
"e": "AQAB",
|
|
598
|
+
"x5c": [
|
|
599
|
+
"MIIClzCCAX8CBgGZY0bqrTANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDDARpanVk..."
|
|
600
|
+
],
|
|
601
|
+
"x5t": "MHfVp4kBjEZuYOtiaaGsfLCL15Q",
|
|
602
|
+
"x5t#S256": "QADezSLgD8emuonBz8hn8ghTnxo7AHX4NVNkr4luEhk"
|
|
603
|
+
},
|
|
604
|
+
...
|
|
605
|
+
]
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
The signature key is returned in its original *JWK* (JSON Web Key) format, or converted to
|
|
609
|
+
either *DER* (Distinguished Encoding Rules) or *PEM* (Privacy-Enhanced Mail) format, as per *ftm*.
|
|
610
|
+
|
|
611
|
+
:param token: the reference token
|
|
612
|
+
:param fmt: the returning key's format
|
|
613
|
+
:param errors: incidental error messages
|
|
614
|
+
:param logger: optional logger
|
|
615
|
+
:return: the public key in *JWT*, *DER*, or *PEM* format, or *None* if error
|
|
616
|
+
"""
|
|
617
|
+
from pypomes_crypto import crypto_jwk_convert
|
|
618
|
+
|
|
619
|
+
# initialize the return variable
|
|
620
|
+
result: dict[str, str] | str | None = None
|
|
621
|
+
|
|
622
|
+
claims: dict[str, Any] = jwt_get_claims(token=token,
|
|
623
|
+
errors=errors,
|
|
624
|
+
logger=logger)
|
|
625
|
+
if not errors:
|
|
626
|
+
# obtain the JWKS (JSON Web Key Set) from the token issuer
|
|
627
|
+
issuer: str = claims["payload"].get("iss")
|
|
628
|
+
url: str = f"{issuer}/protocol/openid-connect/certs"
|
|
629
|
+
if logger:
|
|
630
|
+
logger.debug(msg=f"GET {url}")
|
|
631
|
+
try:
|
|
632
|
+
response: requests.Response = requests.get(url=url)
|
|
633
|
+
if response.status_code == 200:
|
|
634
|
+
# request succeeded
|
|
635
|
+
if logger:
|
|
636
|
+
logger.debug(msg=f"GET success, status {response.status_code}")
|
|
637
|
+
# select the appropriate JWK
|
|
638
|
+
reply: dict[str, list[dict[str, str]]] = response.json()
|
|
639
|
+
jwk: dict[str, str] | None = None
|
|
640
|
+
for key in reply["keys"]:
|
|
641
|
+
if key.get("use") == "sig":
|
|
642
|
+
jwk = key
|
|
643
|
+
break
|
|
644
|
+
if jwk:
|
|
645
|
+
# convert from 'JWK' to 'PEM' and save it for further use
|
|
646
|
+
if fmt in ["DER", "PEM"]:
|
|
647
|
+
# noinspection PyTypeChecker
|
|
648
|
+
result = crypto_jwk_convert(jwk=jwk,
|
|
649
|
+
fmt=fmt)
|
|
650
|
+
else:
|
|
651
|
+
result = jwk
|
|
652
|
+
if fmt and logger:
|
|
653
|
+
logger.debug(f"Public key obtained for isuer '{issuer}'")
|
|
654
|
+
else:
|
|
655
|
+
msg: str = (f"Signature public key missing from the JWKS "
|
|
656
|
+
f"returned by the token issuer '{issuer}'")
|
|
657
|
+
if logger:
|
|
658
|
+
logger.error(msg=msg)
|
|
659
|
+
if isinstance(errors, list):
|
|
660
|
+
errors.append(msg)
|
|
661
|
+
elif logger:
|
|
662
|
+
msg: str = f"GET failure, status {response.status_code}, reason {response.reason}"
|
|
663
|
+
if hasattr(response, "content") and response.content:
|
|
664
|
+
msg += f", content {response.content}"
|
|
665
|
+
logger.error(msg=msg)
|
|
666
|
+
if isinstance(errors, list):
|
|
667
|
+
errors.append(msg)
|
|
668
|
+
except Exception as e:
|
|
669
|
+
# the operation raised an exception
|
|
670
|
+
msg = exc_format(exc=e,
|
|
671
|
+
exc_info=sys.exc_info())
|
|
672
|
+
if logger:
|
|
673
|
+
logger.error(msg=msg)
|
|
674
|
+
if isinstance(errors, list):
|
|
675
|
+
errors.append(msg)
|
|
676
|
+
|
|
677
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A collection of Python pomes, penyeach (JWT module)
|
|
5
5
|
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-JWT
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-JWT/issues
|
|
@@ -13,5 +13,6 @@ Requires-Python: >=3.12
|
|
|
13
13
|
Requires-Dist: cryptography>=46.0.3
|
|
14
14
|
Requires-Dist: flask>=3.1.2
|
|
15
15
|
Requires-Dist: pyjwt>=2.10.1
|
|
16
|
-
Requires-Dist: pypomes-core>=2.
|
|
16
|
+
Requires-Dist: pypomes-core>=2.8.1
|
|
17
|
+
Requires-Dist: pypomes-crypto>=0.4.8
|
|
17
18
|
Requires-Dist: pypomes-db>=2.8.1
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pypomes_jwt/__init__.py,sha256=VvDSmB6PINjpsYxSgg302dwpKAC0sZjV-2iZr1Oc6uo,862
|
|
2
|
+
pypomes_jwt/jwt_config.py,sha256=ypr7BCRp1slJ503iyVmma-ljbaZAnbk_qpZKNRjD5CI,4026
|
|
3
|
+
pypomes_jwt/jwt_pomes.py,sha256=IDcipM0ckFpoJOz2dbcTbp46gLKrQ1tfvP2ErWTXceA,28464
|
|
4
|
+
pypomes_jwt/jwt_registry.py,sha256=ypBEoL0I2F08sR2G2VO9wXxVeE252lNzjIAC3FGORhA,22631
|
|
5
|
+
pypomes_jwt-1.4.0.dist-info/METADATA,sha256=H7mM6PK66nfdOFxShbslmmUDzB_MmFj4f1LMsfjw_x8,697
|
|
6
|
+
pypomes_jwt-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-1.4.0.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-1.4.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pypomes_jwt/__init__.py,sha256=esLvNt3Vr4WiZlx1lqKbLIXpDhdBFjqhqKUM6laSwg4,820
|
|
2
|
-
pypomes_jwt/jwt_config.py,sha256=ypr7BCRp1slJ503iyVmma-ljbaZAnbk_qpZKNRjD5CI,4026
|
|
3
|
-
pypomes_jwt/jwt_pomes.py,sha256=sje_1kTSp4I8MEw7jlwYHrEX2JB7a8iEqwt4curfvqc,23659
|
|
4
|
-
pypomes_jwt/jwt_registry.py,sha256=ypBEoL0I2F08sR2G2VO9wXxVeE252lNzjIAC3FGORhA,22631
|
|
5
|
-
pypomes_jwt-1.3.9.dist-info/METADATA,sha256=Rf63Qn0O4FSB-IHmnjZDgI39_Mq5_VRreon8QKYhgpA,660
|
|
6
|
-
pypomes_jwt-1.3.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-1.3.9.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-1.3.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|