Skip to content

Commit 1473851

Browse files
committed
Add linter
1 parent a4ffe80 commit 1473851

10 files changed

Lines changed: 142 additions & 64 deletions

File tree

.github/workflows/pythonpackage.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@ env:
1010
FORCE_COLOR: 1
1111

1212
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Setup uv
19+
uses: astral-sh/setup-uv@v6
20+
21+
- name: Install dependencies
22+
run: uv sync --frozen
23+
24+
- name: Run ruff check
25+
run: uv run ruff check jwt_rsa tests
26+
27+
- name: Run ruff format check
28+
run: uv run ruff format --check jwt_rsa tests
29+
1330
mypy:
1431
runs-on: ubuntu-latest
1532
strategy:

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
.PHONY: test tests mypy pytest lint
1+
.PHONY: test tests mypy pytest lint format
2+
3+
format:
4+
uv run --group dev ruff check --fix jwt_rsa tests
5+
uv run --group dev ruff format jwt_rsa tests
26

37
lint:
48
uv run --group dev ruff check jwt_rsa tests
9+
uv run --group dev ruff format --check jwt_rsa tests
510

611
mypy:
712
uv run --group dev mypy jwt_rsa

jwt_rsa/cli.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,32 @@ class Formatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionH
1515

1616

1717
parser = ArgumentParser(formatter_class=Formatter)
18+
parser.add_argument("-a", "--algorithm", choices=ALGORITHMS, help="Algorithm for JWT keys", default="RS512")
1819
parser.add_argument(
19-
"-a", "--algorithm", choices=ALGORITHMS,
20-
help="Algorithm for JWT keys", default="RS512"
21-
)
22-
parser.add_argument(
23-
"--log-level", choices=["debug", "info", "warning", "error", "critical"],
24-
type=lambda x: getattr(logging, x.upper(), logging.INFO), default=logging.INFO,
20+
"--log-level",
21+
choices=["debug", "info", "warning", "error", "critical"],
22+
type=lambda x: getattr(logging, x.upper(), logging.INFO),
23+
default=logging.INFO,
2524
)
2625

2726
subparsers = parser.add_subparsers(dest="command", required=True)
2827

2928
keygen_parser = subparsers.add_parser("keygen", help="Generate a new RSA key pair", formatter_class=Formatter)
3029
keygen_parser.set_defaults(func=keygen.main)
3130
keygen_parser.add_argument(
32-
"-b", "--bits", dest="bits", type=int, default=2048, choices=[2 ** i for i in range(10, 14)],
31+
"-b",
32+
"--bits",
33+
dest="bits",
34+
type=int,
35+
default=2048,
36+
choices=[2**i for i in range(10, 14)],
3337
)
3438
keygen_parser.add_argument(
35-
"--kid", dest="kid", type=str, default="", help="Key ID, will be generated if missing",
39+
"--kid",
40+
dest="kid",
41+
type=str,
42+
default="",
43+
help="Key ID, will be generated if missing",
3644
)
3745
keygen_parser.add_argument("-u", "--use", dest="use", type=str, default="sig", choices=["sig", "enc"])
3846
keygen_parser.add_argument("-o", "--format", choices=["pem", "jwk", "base64"], default="jwk")
@@ -69,23 +77,28 @@ class Formatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionH
6977
" * 'y' means years\n"
7078
)
7179
issue_parser = subparsers.add_parser(
72-
"issue",
73-
help="Issue a new JWT token\n",
74-
description=ISSUE_PARSER_DESCRIPTION,
75-
formatter_class=Formatter
80+
"issue", help="Issue a new JWT token\n", description=ISSUE_PARSER_DESCRIPTION, formatter_class=Formatter
7681
)
7782
issue_parser.add_argument(
78-
"-K", "--private-key", required=True,
79-
help="Private JWT key", type=Path,
83+
"-K",
84+
"--private-key",
85+
required=True,
86+
help="Private JWT key",
87+
type=Path,
8088
)
8189
issue_parser.add_argument("--expired", help="Token expiration", type=parse_interval, default="+1M")
8290
issue_parser.add_argument("--nbf", help="Token nbf claim", type=parse_interval, default="-1m")
8391
issue_parser.add_argument(
84-
"-I", "--no-interactive", action="store_false", dest="interactive",
92+
"-I",
93+
"--no-interactive",
94+
action="store_false",
95+
dest="interactive",
8596
help="No interactive mode, do not open editor for claims, just read JSON from stdin",
8697
)
8798
issue_parser.add_argument(
88-
"-e", "--editor", help="Editor to use in interactive mode",
99+
"-e",
100+
"--editor",
101+
help="Editor to use in interactive mode",
89102
default=os.getenv("EDITOR", "vim"),
90103
)
91104
issue_parser.set_defaults(func=issue.main)
@@ -96,7 +109,10 @@ class Formatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionH
96109
verify_parser.add_argument("-k", "--public-key", required=False, help="Public key", type=Path)
97110
verify_parser.add_argument("-V", "--no-verify", action="store_false", help="No verify signature", dest="verify")
98111
verify_parser.add_argument(
99-
"-I", "--no-interactive", action="store_false", dest="interactive",
112+
"-I",
113+
"--no-interactive",
114+
action="store_false",
115+
dest="interactive",
100116
help="Interactive mode or raw read token from stdin",
101117
)
102118
verify_parser.set_defaults(func=verify.main)

jwt_rsa/convert.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ def generate_kid(key: RSAPrivateKey) -> str:
1919

2020

2121
def convert(
22-
private: RSAPrivateKey, public: RSAPublicKey,
22+
private: RSAPrivateKey,
23+
public: RSAPublicKey,
2324
fmt: Literal["pem", "jwk", "base64"],
24-
pretty: bool = False, algorithm: AlgorithmType = "RS512",
25+
pretty: bool = False,
26+
algorithm: AlgorithmType = "RS512",
2527
) -> tuple[str, str]:
2628
if fmt == "pem":
2729
return (

jwt_rsa/issue.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def main(arguments: SimpleNamespace) -> None:
9696
load_private_key(arguments.private_key),
9797
expires=arguments.expired,
9898
nbf_delta=-arguments.nbf,
99-
algorithm=arguments.algorithm
99+
algorithm=arguments.algorithm,
100100
)
101101

102102
whoami = pwd.getpwuid(os.getuid())
@@ -124,7 +124,8 @@ def main(arguments: SimpleNamespace) -> None:
124124
with NamedTemporaryFile("wt", suffix=".py") as fp:
125125
if arguments.interactive:
126126
fp.write(
127-
TEMPLATE % {
127+
TEMPLATE
128+
% {
128129
"exp": arguments.expired,
129130
"nbf": arguments.nbf,
130131
"preamble": preable,

jwt_rsa/jwks.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def parse_jwk(self, jwk: dict[str, Any]) -> dict[str, RSAPublicKey]:
4343
if loaded_key is None:
4444
log.warning("Skipping JWK key: %s", key)
4545
continue
46-
keys[jwk['kid']] = loaded_key
46+
keys[jwk["kid"]] = loaded_key
4747
return keys
4848

4949
@abstractmethod
@@ -58,10 +58,10 @@ def decoder(self, kid: str) -> JWTDecoder:
5858

5959
def decode(self, token: str) -> dict[str, Any]:
6060
header = self.jws.get_unverified_header(token)
61-
if 'kid' not in header:
61+
if "kid" not in header:
6262
raise ValueError("Token does not contain 'kid' header")
6363

64-
decoder = self.decoder(header['kid'])
64+
decoder = self.decoder(header["kid"])
6565
return decoder.decode(token)
6666

6767

@@ -74,11 +74,7 @@ def __init__(self, url: str, *, ssl_context: ssl.SSLContext | None = None, **kwa
7474

7575
def refresh(self) -> None:
7676
url_parts = urlparse(self.url)
77-
host, port = (
78-
url_parts.netloc.split(":")
79-
if ":" in url_parts.netloc
80-
else (url_parts.netloc, 443)
81-
)
77+
host, port = url_parts.netloc.split(":") if ":" in url_parts.netloc else (url_parts.netloc, 443)
8278
with closing(self.CLIENT_CLASS(host, int(port), context=self.ssl_context)) as client:
8379
client.request("GET", url_parts.path)
8480
response = client.getresponse()
@@ -91,6 +87,7 @@ def refresh(self) -> None:
9187

9288
def main(args: argparse.Namespace) -> None:
9389
from .rsa import rsa_to_jwk
90+
9491
fetcher = HTTPSJWKFetcher(args.url)
9592
fetcher.refresh()
9693

jwt_rsa/rsa.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def load_jwk(jwk: RSAJWKPublicKey | RSAJWKPrivateKey | str) -> JWKKeyPair:
9898
jwk_dict = jwk
9999

100100
if "d" in jwk_dict: # Private key
101-
private_key = load_jwk_private_key(jwk_dict) # type: ignore
101+
private_key = load_jwk_private_key(jwk_dict) # type: ignore
102102
public_key = private_key.public_key()
103103
else: # Public key
104104
public_key = load_jwk_public_key(jwk_dict)
@@ -108,20 +108,32 @@ def load_jwk(jwk: RSAJWKPublicKey | RSAJWKPrivateKey | str) -> JWKKeyPair:
108108

109109

110110
def int_to_base64url(value: int) -> str:
111-
return base64.urlsafe_b64encode(
112-
value.to_bytes((value.bit_length() + 7) // 8, byteorder="big"),
113-
).rstrip(b"=").decode("ascii")
111+
return (
112+
base64.urlsafe_b64encode(
113+
value.to_bytes((value.bit_length() + 7) // 8, byteorder="big"),
114+
)
115+
.rstrip(b"=")
116+
.decode("ascii")
117+
)
114118

115119

116120
@overload
117121
def rsa_to_jwk(
118-
key: RSAPublicKey, *, kid: str = "", alg: AlgorithmType = "RS256", use: str = "sig",
122+
key: RSAPublicKey,
123+
*,
124+
kid: str = "",
125+
alg: AlgorithmType = "RS256",
126+
use: str = "sig",
119127
) -> RSAJWKPublicKey: ...
120128

121129

122130
@overload
123131
def rsa_to_jwk(
124-
key: RSAPrivateKey, *, kid: str = "", alg: AlgorithmType = "RS256", use: str = "sig",
132+
key: RSAPrivateKey,
133+
*,
134+
kid: str = "",
135+
alg: AlgorithmType = "RS256",
136+
use: str = "sig",
125137
) -> RSAJWKPrivateKey: ...
126138

127139

jwt_rsa/token.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class JWTDecoder:
5151
def __init__(
5252
self,
5353
key: RSAPublicKey,
54-
*, options: OptionsType | None = None,
54+
*,
55+
options: OptionsType | None = None,
5556
expires: int | float = DEFAULT_EXPIRATION,
5657
nbf_delta: int | float = NBF_DELTA,
5758
algorithm: AlgorithmType = "RS512",
@@ -66,8 +67,11 @@ def __init__(
6667

6768
def decode(self, token: str, verify: bool = True, **kwargs: Any) -> dict[str, Any]:
6869
return self.jwt.decode(
69-
token, key=self.public_key, verify=verify,
70-
algorithms=list(self.algorithms), **kwargs,
70+
token,
71+
key=self.public_key,
72+
verify=verify,
73+
algorithms=list(self.algorithms),
74+
**kwargs,
7175
)
7276

7377

@@ -84,7 +88,7 @@ def encode(
8488
expired: DateType | EllipsisType = ...,
8589
nbf: DateType | EllipsisType = ...,
8690
headers: dict[str, Any] | None = None,
87-
**claims: Any
91+
**claims: Any,
8892
) -> str:
8993
claims.setdefault("exp", int(date_to_timestamp(expired, lambda: time.time() + self.expires)))
9094
claims.setdefault("nbf", int(date_to_timestamp(nbf, lambda: time.time() - self.nbf_delta, timedelta_func=sub)))
@@ -93,7 +97,8 @@ def encode(
9397

9498
@overload
9599
def JWT(
96-
key: RSAPrivateKey, *,
100+
key: RSAPrivateKey,
101+
*,
97102
options: OptionsType | None = None,
98103
expires: int | float = DEFAULT_EXPIRATION,
99104
nbf_delta: int | float = NBF_DELTA,
@@ -104,7 +109,8 @@ def JWT(
104109

105110
@overload
106111
def JWT(
107-
key: RSAPublicKey, *,
112+
key: RSAPublicKey,
113+
*,
108114
options: OptionsType | None = None,
109115
expires: int | float = DEFAULT_EXPIRATION,
110116
nbf_delta: int | float = NBF_DELTA,

tests/test_cli.py

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ def test_pem_format(capsys):
5656

5757
stdout, stderr = capsys.readouterr()
5858

59-
public_bytes, private_bytes = [
60-
x.strip().encode() for x in stdout.split("\n\n", 1)
61-
]
59+
public_bytes, private_bytes = [x.strip().encode() for x in stdout.split("\n\n", 1)]
6260

6361
public = serialization.load_pem_public_key(public_bytes, default_backend())
6462
private = serialization.load_pem_private_key(
65-
private_bytes, None, default_backend(),
63+
private_bytes,
64+
None,
65+
default_backend(),
6666
)
6767

6868
payload = os.urandom(1024 * 16)
@@ -92,10 +92,17 @@ def test_keygen_no_force(capsys, tmp_path):
9292
public_path = tmp_path / "public.pem"
9393

9494
keygen(
95-
parser.parse_args([
96-
"keygen", "-o", "pem",
97-
"-K", str(private_path), "-k", str(public_path),
98-
]),
95+
parser.parse_args(
96+
[
97+
"keygen",
98+
"-o",
99+
"pem",
100+
"-K",
101+
str(private_path),
102+
"-k",
103+
str(public_path),
104+
]
105+
),
99106
)
100107

101108
assert private_path.exists()
@@ -109,20 +116,35 @@ def test_keygen_no_force(capsys, tmp_path):
109116

110117
# Try to generate keys again buy don't overwrite existing
111118
keygen(
112-
parser.parse_args([
113-
"keygen", "-o", "pem",
114-
"-K", str(private_path), "-k", str(public_path),
115-
]),
119+
parser.parse_args(
120+
[
121+
"keygen",
122+
"-o",
123+
"pem",
124+
"-K",
125+
str(private_path),
126+
"-k",
127+
str(public_path),
128+
]
129+
),
116130
)
117131

118132
assert public_content == public_path.read_text()
119133
assert private_content == private_path.read_text()
120134

121135
keygen(
122-
parser.parse_args([
123-
"keygen", "-o", "pem", "-f",
124-
"-K", str(private_path), "-k", str(public_path),
125-
]),
136+
parser.parse_args(
137+
[
138+
"keygen",
139+
"-o",
140+
"pem",
141+
"-f",
142+
"-K",
143+
str(private_path),
144+
"-k",
145+
str(public_path),
146+
]
147+
),
126148
)
127149

128150
assert public_content != public_path.read_text()

0 commit comments

Comments
 (0)