Skip to content

Commit 57a3d40

Browse files
committed
feat(auth): require keyword args for load_from_keychain identity resolution
Refactored load_from_keychain to accept `client_id` or `username` as keyword arguments instead of prompting the user directly. Improves automation and unifies behavior across credential provider types.
1 parent d9d3aa9 commit 57a3d40

File tree

1 file changed

+63
-21
lines changed

1 file changed

+63
-21
lines changed

src/jamf_pro_sdk/clients/auth.py

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import datetime, timedelta, timezone
44
from getpass import getpass
55
from threading import Lock
6-
from typing import TYPE_CHECKING, Any, Dict, Optional, Type
6+
from typing import TYPE_CHECKING, Any, Dict, Optional, Type, overload
77

88
try:
99
import boto3
@@ -227,6 +227,18 @@ def _request_access_token(self) -> AccessToken:
227227
return AccessToken(type="user", **resp.json())
228228

229229

230+
@overload
231+
def prompt_for_credentials(
232+
provider_type: Type[UserCredentialsProvider],
233+
) -> UserCredentialsProvider: ...
234+
235+
236+
@overload
237+
def prompt_for_credentials(
238+
provider_type: Type[ApiClientCredentialsProvider],
239+
) -> ApiClientCredentialsProvider: ...
240+
241+
230242
def prompt_for_credentials(provider_type: Type[CredentialsProvider]) -> CredentialsProvider:
231243
"""Prompts the user for credentials based on the given provider type.
232244
@@ -311,30 +323,58 @@ def load_from_aws_secrets_manager(
311323
return provider_type(**credentials)
312324

313325

326+
@overload
327+
def load_from_keychain(
328+
provider_type: Type[UserCredentialsProvider],
329+
server: str,
330+
*,
331+
username: str,
332+
client_id: None = None,
333+
) -> UserCredentialsProvider: ...
334+
335+
336+
@overload
314337
def load_from_keychain(
315-
provider_type: Type[CredentialsProvider], server: str
338+
provider_type: Type[ApiClientCredentialsProvider],
339+
server: str,
340+
*,
341+
client_id: str,
342+
username: None = None,
343+
) -> ApiClientCredentialsProvider: ...
344+
345+
346+
def load_from_keychain(
347+
provider_type: Type[CredentialsProvider],
348+
server: str,
349+
client_id: Optional[str] = None,
350+
username: Optional[str] = None,
316351
) -> CredentialsProvider:
317352
"""Load credentials from the macOS login keychain and return an instance of the
318353
specified credentials provider.
319354
355+
.. important::
356+
357+
This credentials provider requires the ``macOS`` extra dependency.
358+
320359
The Jamf Pro API password or client credentials are stored in the keychain with
321360
the ``service_name`` set to the Jamf Pro server name.
322361
323362
Supports:
324-
- ``UserCredentialsProvider``: retrieves password using provided username
325-
- ``ApiClientCredentialsProvider``: retrieves Client ID and Client Secret using usernames of
326-
"CLIENT_ID" and "CLIENT_SECRET"
327-
328-
.. important::
329-
330-
This credentials provider requires the ``macOS`` extra dependency.
363+
- ``UserCredentialsProvider``: Retrieves a password using the provided ``username``.
364+
- ``ApiClientCredentialsProvider``: Retrieves the API client secret using the provided ``client_id``.
331365
332366
:param provider_type: The credentials provider class to instantiate
333367
:type provider_type: Type[CredentialsProvider]
334368
335369
:param server: The Jamf Pro server name.
336370
:type server: str
337371
372+
:param client_id: The client ID used for ``ApiClientCredentialsProvider``. Required if ``provider_type`` is that provider.
373+
:type client_id: Optional[str]
374+
375+
:param username: The username used for ``UserCredentialsProvider``. Required if ``provider_type`` is that provider.
376+
:type username: Optional[str]
377+
338378
:return: An instantiated credentials provider using the keychain values.
339379
:rtype: CredentialsProvider
340380
"""
@@ -347,20 +387,22 @@ def load_from_keychain(
347387
server = f"https://{server}"
348388

349389
if issubclass(provider_type, UserCredentialsProvider):
350-
username = input("Jamf Pro Username: ")
351-
password = keyring.get_password(service_name=server, username=username)
352-
if password is None:
353-
raise CredentialsError(
354-
f"Password not found for server {server} and username {username}"
390+
if username is None:
391+
raise ValueError(
392+
"Username argument is required to create UserCredentialsProvider object."
355393
)
356-
return provider_type(username, password)
394+
identity = username
357395
elif issubclass(provider_type, ApiClientCredentialsProvider):
358-
client_id = keyring.get_password(service_name=server, username="CLIENT_ID")
359-
client_secret = keyring.get_password(service_name=server, username="CLIENT_SECRET")
360-
if not client_id or not client_secret:
361-
raise CredentialsError(
362-
f"API Credentials were not found for server {server}. Please verify they are in the correct format."
396+
if client_id is None:
397+
raise ValueError(
398+
"API Client ID is required to instantiate ApiClientCredentialsProvider."
363399
)
364-
return provider_type(client_id, client_secret)
400+
identity = client_id
365401
else:
366402
raise TypeError(f"Unsupported credentials provider: {provider_type}")
403+
404+
password = keyring.get_password(service_name=server, username=identity)
405+
if password is None:
406+
raise CredentialsError(f"Password not found for server {server} and username {identity}")
407+
408+
return provider_type(identity, password)

0 commit comments

Comments
 (0)