Skip to content

Commit b7dce13

Browse files
committed
refactor(google): use strategy-based token_command
1 parent f14ef82 commit b7dce13

2 files changed

Lines changed: 32 additions & 34 deletions

File tree

docs/config.rst

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -291,16 +291,19 @@ There are two ways to handle OAuth authentication with Google:
291291
executed to obtain a fresh access token on demand. Vdirsyncer passes the
292292
token as a ``Bearer`` header and re-runs the command on 401 responses.
293293

294-
With ``token_command``, you do not need to register a Google Cloud project or
295-
install ``aiohttp-oauthlib``. Any OAuth helper that prints an access token to
296-
stdout works, e.g. `oama <https://github.com/pdobsan/oama>`_::
294+
With ``token_command``, vdirsyncer doesn't need OAuth credentials or
295+
``aiohttp-oauthlib``. The external helper handles Google registration. The format is
296+
``["strategy", "arg1", ...]``, same as :ref:`the .fetch config values
297+
<fetch_params>`. Supported strategies are ``command`` (exec directly) and
298+
``shell`` (run via shell)::
297299

298300
[storage my_google_cal]
299301
type = "google_calendar"
300-
token_command = ["oama", "access", "my-google-account"]
302+
token_command = ["command", "oama", "access", "my-google-account"]
301303

302-
The command can be specified as a list (executed directly) or a string (run via
303-
shell).
304+
Or using a shell command::
305+
306+
token_command = ["shell", "oama access my-google-account"]
304307

305308
Built-in OAuth setup
306309
++++++++++++++++++++
@@ -370,10 +373,12 @@ itself or write anything to it.
370373
:param client_id/client_secret: OAuth credentials, obtained from the Google
371374
API Manager. Optional if ``token_command``
372375
is set.
373-
:param token_command: Command to fetch an OAuth access token. Can be a list
374-
of arguments or a string (run via shell). The command
375-
must print a valid bearer token to stdout. When set,
376-
``token_file`` and OAuth credentials are not required.
376+
:param token_command: Fetch strategy for OAuth access token, same format as
377+
``.fetch`` params: ``["strategy", "arg1", ...]``.
378+
Supported strategies: ``command``, ``shell``.
379+
The command must print a valid bearer token to stdout.
380+
When set, ``token_file`` and OAuth credentials are not
381+
required.
377382

378383
.. storage:: google_contacts
379384

@@ -393,10 +398,12 @@ itself or write anything to it.
393398
:param client_id/client_secret: OAuth credentials, obtained from the Google
394399
API Manager. Optional if ``token_command``
395400
is set.
396-
:param token_command: Command to fetch an OAuth access token. Can be a list
397-
of arguments or a string (run via shell). The command
398-
must print a valid bearer token to stdout. When set,
399-
``token_file`` and OAuth credentials are not required.
401+
:param token_command: Fetch strategy for OAuth access token, same format as
402+
``.fetch`` params: ``["strategy", "arg1", ...]``.
403+
Supported strategies: ``command``, ``shell``.
404+
The command must print a valid bearer token to stdout.
405+
When set, ``token_file`` and OAuth credentials are not
406+
required.
400407

401408
The built-in OAuth flow is not ideal, but Google has deprecated the previous
402409
APIs used for this without providing a suitable replacement. See :gh:`975` for

vdirsyncer/storage/google.py

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,7 @@ def __init__(
4848
connector: aiohttp.BaseConnector,
4949
):
5050
if token_command is not None:
51-
if isinstance(token_command, str):
52-
self._token_command = token_command
53-
self._token_command_shell = True
54-
else:
55-
self._token_command = list(token_command)
56-
self._token_command_shell = False
51+
self._token_command = list(token_command)
5752
else:
5853
self._token_command = None
5954
if not have_oauth2:
@@ -206,27 +201,23 @@ async def _init_token(self):
206201
await self._save_token(self._token)
207202

208203
async def _fetch_token_via_command(self):
209-
import subprocess
204+
from vdirsyncer.cli.fetchparams import STRATEGIES
210205

211206
assert self._token_command is not None
212-
207+
strategy = self._token_command[0]
213208
try:
214-
if self._token_command_shell:
215-
stdout = subprocess.check_output(
216-
self._token_command, text=True, shell=True
217-
)
218-
else:
219-
stdout = subprocess.check_output(self._token_command, text=True)
220-
except OSError as e:
209+
strategy_fn = STRATEGIES[strategy]
210+
except KeyError:
221211
raise exceptions.UserError(
222-
f"Failed to execute token_command: {self._token_command}\n{e!s}"
212+
f"Unknown token_command strategy: {strategy}. "
213+
f"Available: {', '.join(sorted(STRATEGIES))}"
223214
)
224-
225-
self._token = stdout.strip()
226-
if not self._token:
215+
token = strategy_fn(*self._token_command[1:])
216+
if not token:
227217
raise exceptions.UserError(
228-
f"token_command returned empty output: {self._token_command}"
218+
f"token_command strategy '{strategy}' returned empty output"
229219
)
220+
self._token = token
230221
logger.debug("Successfully fetched token via token_command")
231222

232223

0 commit comments

Comments
 (0)