Skip to content

Commit ca169f9

Browse files
committed
feat: Remove JWT parsing and adjust tests
1 parent 7d562d5 commit ca169f9

File tree

4 files changed

+35
-67
lines changed

4 files changed

+35
-67
lines changed

src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/artifacts_helper_credential_provider.py

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,12 @@
55
import os
66
import shutil
77
import subprocess
8-
from dataclasses import dataclass
98
from pathlib import Path
109
from typing import Optional, Union
1110

12-
import jwt
1311
import requests
1412

1513

16-
@dataclass
17-
class Credentials:
18-
"""A set of credentials, consisting of a username and password."""
19-
20-
username: str
21-
password: str
22-
23-
2414
class ArtifactsHelperCredentialProviderError(RuntimeError):
2515
"""Generic error for ArtifactsHelperCredentialProvider."""
2616

@@ -77,26 +67,23 @@ def auth_helper_installed(cls, auth_helper_path: Union[os.PathLike, str]) -> boo
7767
"""Check whether the authentication helper is installed and executable."""
7868
return cls.resolve_auth_helper_path(auth_helper_path) is not None
7969

80-
def get_credentials(self, url: str) -> Optional[Credentials]:
81-
"""Get credentials for the given URL.
70+
def get_token(self, url: str) -> Optional[str]:
71+
"""Get an access token for the given URL.
8272
8373
Args:
8474
url: The URL to retrieve credentials for.
8575
8676
Returns:
87-
The credentials for the URL, or `None` if no credentials could be retrieved.
77+
The token for the URL, or `None` if no credentials could be retrieved.
8878
"""
8979
# Public feed short circuit: return nothing if not getting credentials for the
9080
# upload endpoint (which always requires auth) and the endpoint is public (can
9181
# authenticate without credentials).
9282
if not self._is_upload_endpoint(url) and self._can_authenticate(url, None):
9383
return None
9484

95-
jwt_str = self._get_jwt_from_helper()
96-
if not jwt_str:
97-
return None
98-
99-
return self._get_credentials_from_jwt(jwt_str)
85+
token = self._get_token_from_helper()
86+
return token if token else None
10087

10188
@staticmethod
10289
def _is_upload_endpoint(url) -> bool:
@@ -107,7 +94,7 @@ def _can_authenticate(self, url, auth) -> bool:
10794
response = requests.get(url, auth=auth, timeout=self.timeout)
10895
return response.status_code < 500 and response.status_code not in (401, 403)
10996

110-
def _get_jwt_from_helper(self) -> str:
97+
def _get_token_from_helper(self) -> str:
11198
if self.auth_tool_path is None:
11299
raise ArtifactsHelperCredentialProviderError(
113100
"Failed to get credentials: No authentication tool found"
@@ -140,17 +127,3 @@ def _get_jwt_from_helper(self) -> str:
140127
f"Failed to get credentials: Process {self.auth_tool_path} timed out "
141128
f"after {self.timeout} seconds"
142129
) from e
143-
144-
def _get_credentials_from_jwt(self, jwt_str: str) -> Credentials:
145-
try:
146-
decoded = jwt.decode(
147-
jwt_str, verify=False, options={"verify_signature": False}
148-
)
149-
return Credentials(
150-
username=decoded.get("unique_name", decoded.get("upn", None)),
151-
password=jwt_str,
152-
)
153-
except jwt.PyJWTError as e:
154-
raise ArtifactsHelperCredentialProviderError(
155-
f"Failed to decode JWT: {e}"
156-
) from e

src/artifacts-helper/codespaces_artifacts_helper_keyring/src/codespaces_artifacts_helper_keyring/keyring_backend.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class CodespacesArtifactsHelperKeyringBackend(KeyringBackend):
2121
"pkgs.vsts.me",
2222
)
2323

24+
DEFAULT_USERNAME = "codespaces"
25+
2426
_PROVIDER: Type[ArtifactsHelperCredentialProvider] = (
2527
ArtifactsHelperCredentialProvider
2628
)
@@ -43,10 +45,10 @@ def get_credential(
4345
return None
4446

4547
provider = self._PROVIDER(auth_helper_path=self.AUTH_HELPER_PATH)
46-
creds = provider.get_credentials(service)
47-
if creds is None:
48+
token = provider.get_token(service)
49+
if not token:
4850
return None
49-
return SimpleCredential(creds.username or username, creds.password)
51+
return SimpleCredential(username or self.DEFAULT_USERNAME, token)
5052

5153
def _is_supported_netloc(self, service) -> bool:
5254
try:

src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_artifacts_helper_credential_provider.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
class TestArtifactsHelperWrapper(unittest.TestCase):
1515
SUPPORTED_HOST = "https://pkgs.dev.azure.com/"
1616
TEST_JWT = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cG4iOiJ1cG5AY29udG9zby5jb20iLCJ1bmlxdWVfbmFtZSI6Im5hbWVAY29udG9zby5jb20ifQ.srKYrr5B0i29XERHsvE6mqZpLBzyyrX-gUKe9OHZODw" # noqa: E501
17-
TEST_JWT_USERNAME = "name@contoso.com"
1817

1918
def __init__(self, *args, **kwargs) -> None:
2019
super().__init__(*args, **kwargs)
@@ -61,48 +60,37 @@ def test_auth_helper_installed_and_executable(self):
6160
)
6261
assert ArtifactsHelperCredentialProvider.auth_helper_installed(self.script_path)
6362

64-
def test_get_jwt_from_helper(self):
65-
raw_jwt_value = "raw_jwt_here._-azAZ09"
66-
self.write_script(f"print('{raw_jwt_value}')")
63+
def test_get_token_from_helper(self):
64+
raw_token_value = "raw_token_here._-azAZ09"
65+
self.write_script(f"print('{raw_token_value}')")
6766
provider = ArtifactsHelperCredentialProvider(self.script_path)
68-
assert provider._get_jwt_from_helper() == raw_jwt_value.strip()
67+
assert provider._get_token_from_helper() == raw_token_value.strip()
6968

70-
def test_get_jwt_from_helper_not_installed(self):
69+
def test_get_token_from_helper_not_installed(self):
7170
provider = ArtifactsHelperCredentialProvider()
7271
with pytest.raises(
7372
ArtifactsHelperCredentialProviderError, match="No authentication tool found"
7473
):
75-
provider._get_jwt_from_helper()
74+
provider._get_token_from_helper()
7675

77-
def test_get_credentials_from_jwt(self):
78-
provider = ArtifactsHelperCredentialProvider()
79-
creds = provider._get_credentials_from_jwt(self.TEST_JWT)
80-
assert creds.username == self.TEST_JWT_USERNAME
81-
assert creds.password == self.TEST_JWT
82-
83-
def test_get_credentials(self):
76+
def test_get_token(self):
8477
self.write_script(f"print('{self.TEST_JWT}')")
8578
provider = ArtifactsHelperCredentialProvider(self.script_path)
86-
creds = provider.get_credentials(self.SUPPORTED_HOST)
87-
assert creds.username == self.TEST_JWT_USERNAME
88-
assert creds.password == self.TEST_JWT
79+
assert provider.get_token(self.SUPPORTED_HOST) == self.TEST_JWT
8980

90-
def test_get_credentials_invalid_jwt(self):
91-
self.write_script("print('invalid jwt')")
81+
def test_get_token_invalid_token(self):
82+
self.write_script(r"print('\n\n\n')")
9283
provider = ArtifactsHelperCredentialProvider(self.script_path)
93-
with pytest.raises(
94-
ArtifactsHelperCredentialProviderError, match="Failed to decode JWT:"
95-
):
96-
provider.get_credentials(self.SUPPORTED_HOST)
84+
assert provider.get_token(self.SUPPORTED_HOST) is None
9785

98-
def test_get_crendentials_helper_non_zero_exit(self):
86+
def test_get_token_helper_non_zero_exit(self):
9987
self.write_script("exit(1)")
10088
provider = ArtifactsHelperCredentialProvider(self.script_path)
10189
with pytest.raises(
10290
ArtifactsHelperCredentialProviderError,
10391
match=f"Process .*{self.script_name}.* exited with code 1",
10492
):
105-
provider.get_credentials(self.SUPPORTED_HOST)
93+
provider.get_token(self.SUPPORTED_HOST)
10694

10795

10896
def set_path_executable(path: Union[os.PathLike, str]):

src/artifacts-helper/codespaces_artifacts_helper_keyring/tests/test_keyring_backend.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
ArtifactsHelperCredentialProvider,
88
CodespacesArtifactsHelperKeyringBackend,
99
)
10-
from keyring.credentials import SimpleCredential
1110

1211
# Shouldn't be accessed by tests, but needs to be able
1312
# to get past the quick check.
1413
SUPPORTED_HOST = "https://pkgs.dev.azure.com/"
1514

1615

1716
class FakeProvider(ArtifactsHelperCredentialProvider):
18-
def get_credentials(self, service):
19-
return SimpleCredential("user" + service[-4:], "pass" + service[-4:])
17+
def get_token(self, service):
18+
return "pass" + service[-4:]
2019

2120
@staticmethod
2221
def auth_helper_installed(auth_tool_path):
@@ -83,9 +82,15 @@ def test_get_credential_unsupported_host(only_backend):
8382
assert keyring.get_credential("https://example.com", None) is None
8483

8584

86-
def test_get_credential(only_backend, fake_provider):
85+
def test_get_credential_default_username(only_backend, fake_provider):
86+
creds = keyring.get_credential(SUPPORTED_HOST + "1234", "user12345678")
87+
assert creds.username == "user12345678"
88+
assert creds.password == "pass1234"
89+
90+
91+
def test_get_credential_with_username(only_backend, fake_provider):
8792
creds = keyring.get_credential(SUPPORTED_HOST + "1234", None)
88-
assert creds.username == "user1234"
93+
assert creds.username == "codespaces"
8994
assert creds.password == "pass1234"
9095

9196

@@ -128,7 +133,7 @@ def test_delete_password_fallback(passwords, fake_provider):
128133

129134
def test_cannot_delete_password(passwords, fake_provider):
130135
# Ensure we are getting good credentials
131-
creds = keyring.get_credential(SUPPORTED_HOST + "1234", None)
136+
creds = keyring.get_credential(SUPPORTED_HOST + "1234", "user1234")
132137
assert creds.username == "user1234"
133138
assert creds.password == "pass1234"
134139

0 commit comments

Comments
 (0)