Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
api_jws.py329 linesDownload Raw Back to jwt
1from __future__ import annotations2 3import binascii4import json5import warnings6from typing import TYPE_CHECKING, Any7 8from .algorithms import (9    Algorithm,10    get_default_algorithms,11    has_crypto,12    requires_cryptography,13)14from .exceptions import (15    DecodeError,16    InvalidAlgorithmError,17    InvalidSignatureError,18    InvalidTokenError,19)20from .utils import base64url_decode, base64url_encode21from .warnings import RemovedInPyjwt3Warning22 23if TYPE_CHECKING:24    from .algorithms import AllowedPrivateKeys, AllowedPublicKeys25 26 27class PyJWS:28    header_typ = "JWT"29 30    def __init__(31        self,32        algorithms: list[str] | None = None,33        options: dict[str, Any] | None = None,34    ) -> None:35        self._algorithms = get_default_algorithms()36        self._valid_algs = (37            set(algorithms) if algorithms is not None else set(self._algorithms)38        )39 40        # Remove algorithms that aren't on the whitelist41        for key in list(self._algorithms.keys()):42            if key not in self._valid_algs:43                del self._algorithms[key]44 45        if options is None:46            options = {}47        self.options = {**self._get_default_options(), **options}48 49    @staticmethod50    def _get_default_options() -> dict[str, bool]:51        return {"verify_signature": True}52 53    def register_algorithm(self, alg_id: str, alg_obj: Algorithm) -> None:54        """55        Registers a new Algorithm for use when creating and verifying tokens.56        """57        if alg_id in self._algorithms:58            raise ValueError("Algorithm already has a handler.")59 60        if not isinstance(alg_obj, Algorithm):61            raise TypeError("Object is not of type `Algorithm`")62 63        self._algorithms[alg_id] = alg_obj64        self._valid_algs.add(alg_id)65 66    def unregister_algorithm(self, alg_id: str) -> None:67        """68        Unregisters an Algorithm for use when creating and verifying tokens69        Throws KeyError if algorithm is not registered.70        """71        if alg_id not in self._algorithms:72            raise KeyError(73                "The specified algorithm could not be removed"74                " because it is not registered."75            )76 77        del self._algorithms[alg_id]78        self._valid_algs.remove(alg_id)79 80    def get_algorithms(self) -> list[str]:81        """82        Returns a list of supported values for the 'alg' parameter.83        """84        return list(self._valid_algs)85 86    def get_algorithm_by_name(self, alg_name: str) -> Algorithm:87        """88        For a given string name, return the matching Algorithm object.89 90        Example usage:91 92        >>> jws_obj.get_algorithm_by_name("RS256")93        """94        try:95            return self._algorithms[alg_name]96        except KeyError as e:97            if not has_crypto and alg_name in requires_cryptography:98                raise NotImplementedError(99                    f"Algorithm '{alg_name}' could not be found. Do you have cryptography installed?"100                ) from e101            raise NotImplementedError("Algorithm not supported") from e102 103    def encode(104        self,105        payload: bytes,106        key: AllowedPrivateKeys | str | bytes,107        algorithm: str | None = "HS256",108        headers: dict[str, Any] | None = None,109        json_encoder: type[json.JSONEncoder] | None = None,110        is_payload_detached: bool = False,111        sort_headers: bool = True,112    ) -> str:113        segments = []114 115        # declare a new var to narrow the type for type checkers116        algorithm_: str = algorithm if algorithm is not None else "none"117 118        # Prefer headers values if present to function parameters.119        if headers:120            headers_alg = headers.get("alg")121            if headers_alg:122                algorithm_ = headers["alg"]123 124            headers_b64 = headers.get("b64")125            if headers_b64 is False:126                is_payload_detached = True127 128        # Header129        header: dict[str, Any] = {"typ": self.header_typ, "alg": algorithm_}130 131        if headers:132            self._validate_headers(headers)133            header.update(headers)134 135        if not header["typ"]:136            del header["typ"]137 138        if is_payload_detached:139            header["b64"] = False140        elif "b64" in header:141            # True is the standard value for b64, so no need for it142            del header["b64"]143 144        json_header = json.dumps(145            header, separators=(",", ":"), cls=json_encoder, sort_keys=sort_headers146        ).encode()147 148        segments.append(base64url_encode(json_header))149 150        if is_payload_detached:151            msg_payload = payload152        else:153            msg_payload = base64url_encode(payload)154        segments.append(msg_payload)155 156        # Segments157        signing_input = b".".join(segments)158 159        alg_obj = self.get_algorithm_by_name(algorithm_)160        key = alg_obj.prepare_key(key)161        signature = alg_obj.sign(signing_input, key)162 163        segments.append(base64url_encode(signature))164 165        # Don't put the payload content inside the encoded token when detached166        if is_payload_detached:167            segments[1] = b""168        encoded_string = b".".join(segments)169 170        return encoded_string.decode("utf-8")171 172    def decode_complete(173        self,174        jwt: str | bytes,175        key: AllowedPublicKeys | str | bytes = "",176        algorithms: list[str] | None = None,177        options: dict[str, Any] | None = None,178        detached_payload: bytes | None = None,179        **kwargs,180    ) -> dict[str, Any]:181        if kwargs:182            warnings.warn(183                "passing additional kwargs to decode_complete() is deprecated "184                "and will be removed in pyjwt version 3. "185                f"Unsupported kwargs: {tuple(kwargs.keys())}",186                RemovedInPyjwt3Warning,187            )188        if options is None:189            options = {}190        merged_options = {**self.options, **options}191        verify_signature = merged_options["verify_signature"]192 193        if verify_signature and not algorithms:194            raise DecodeError(195                'It is required that you pass in a value for the "algorithms" argument when calling decode().'196            )197 198        payload, signing_input, header, signature = self._load(jwt)199 200        if header.get("b64", True) is False:201            if detached_payload is None:202                raise DecodeError(203                    'It is required that you pass in a value for the "detached_payload" argument to decode a message having the b64 header set to false.'204                )205            payload = detached_payload206            signing_input = b".".join([signing_input.rsplit(b".", 1)[0], payload])207 208        if verify_signature:209            self._verify_signature(signing_input, header, signature, key, algorithms)210 211        return {212            "payload": payload,213            "header": header,214            "signature": signature,215        }216 217    def decode(218        self,219        jwt: str | bytes,220        key: AllowedPublicKeys | str | bytes = "",221        algorithms: list[str] | None = None,222        options: dict[str, Any] | None = None,223        detached_payload: bytes | None = None,224        **kwargs,225    ) -> Any:226        if kwargs:227            warnings.warn(228                "passing additional kwargs to decode() is deprecated "229                "and will be removed in pyjwt version 3. "230                f"Unsupported kwargs: {tuple(kwargs.keys())}",231                RemovedInPyjwt3Warning,232            )233        decoded = self.decode_complete(234            jwt, key, algorithms, options, detached_payload=detached_payload235        )236        return decoded["payload"]237 238    def get_unverified_header(self, jwt: str | bytes) -> dict[str, Any]:239        """Returns back the JWT header parameters as a dict()240 241        Note: The signature is not verified so the header parameters242        should not be fully trusted until signature verification is complete243        """244        headers = self._load(jwt)[2]245        self._validate_headers(headers)246 247        return headers248 249    def _load(self, jwt: str | bytes) -> tuple[bytes, bytes, dict[str, Any], bytes]:250        if isinstance(jwt, str):251            jwt = jwt.encode("utf-8")252 253        if not isinstance(jwt, bytes):254            raise DecodeError(f"Invalid token type. Token must be a {bytes}")255 256        try:257            signing_input, crypto_segment = jwt.rsplit(b".", 1)258            header_segment, payload_segment = signing_input.split(b".", 1)259        except ValueError as err:260            raise DecodeError("Not enough segments") from err261 262        try:263            header_data = base64url_decode(header_segment)264        except (TypeError, binascii.Error) as err:265            raise DecodeError("Invalid header padding") from err266 267        try:268            header = json.loads(header_data)269        except ValueError as e:270            raise DecodeError(f"Invalid header string: {e}") from e271 272        if not isinstance(header, dict):273            raise DecodeError("Invalid header string: must be a json object")274 275        try:276            payload = base64url_decode(payload_segment)277        except (TypeError, binascii.Error) as err:278            raise DecodeError("Invalid payload padding") from err279 280        try:281            signature = base64url_decode(crypto_segment)282        except (TypeError, binascii.Error) as err:283            raise DecodeError("Invalid crypto padding") from err284 285        return (payload, signing_input, header, signature)286 287    def _verify_signature(288        self,289        signing_input: bytes,290        header: dict[str, Any],291        signature: bytes,292        key: AllowedPublicKeys | str | bytes = "",293        algorithms: list[str] | None = None,294    ) -> None:295        try:296            alg = header["alg"]297        except KeyError:298            raise InvalidAlgorithmError("Algorithm not specified")299 300        if not alg or (algorithms is not None and alg not in algorithms):301            raise InvalidAlgorithmError("The specified alg value is not allowed")302 303        try:304            alg_obj = self.get_algorithm_by_name(alg)305        except NotImplementedError as e:306            raise InvalidAlgorithmError("Algorithm not supported") from e307        prepared_key = alg_obj.prepare_key(key)308 309        if not alg_obj.verify(signing_input, prepared_key, signature):310            raise InvalidSignatureError("Signature verification failed")311 312    def _validate_headers(self, headers: dict[str, Any]) -> None:313        if "kid" in headers:314            self._validate_kid(headers["kid"])315 316    def _validate_kid(self, kid: Any) -> None:317        if not isinstance(kid, str):318            raise InvalidTokenError("Key ID header parameter must be a string")319 320 321_jws_global_obj = PyJWS()322encode = _jws_global_obj.encode323decode_complete = _jws_global_obj.decode_complete324decode = _jws_global_obj.decode325register_algorithm = _jws_global_obj.register_algorithm326unregister_algorithm = _jws_global_obj.unregister_algorithm327get_algorithm_by_name = _jws_global_obj.get_algorithm_by_name328get_unverified_header = _jws_global_obj.get_unverified_header329 
codekingpro/portable-devtools · Team Ai