Skip to content

Commit 398c526

Browse files
committed
refactor(google): use strategy-based token_command
1 parent 498d812 commit 398c526

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
@@ -302,16 +302,19 @@ There are two ways to handle OAuth authentication with Google:
302302
executed to obtain a fresh access token on demand. Vdirsyncer passes the
303303
token as a ``Bearer`` header and re-runs the command on 401 responses.
304304

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

309311
[storage my_google_cal]
310312
type = "google_calendar"
311-
token_command = ["oama", "access", "my-google-account"]
313+
token_command = ["command", "oama", "access", "my-google-account"]
312314

313-
The command can be specified as a list (executed directly) or a string (run via
314-
shell).
315+
Or using a shell command::
316+
317+
token_command = ["shell", "oama access my-google-account"]
315318

316319
Built-in OAuth setup
317320
++++++++++++++++++++
@@ -381,10 +384,12 @@ itself or write anything to it.
381384
:param client_id/client_secret: OAuth credentials, obtained from the Google
382385
API Manager. Optional if ``token_command``
383386
is set.
384-
:param token_command: Command to fetch an OAuth access token. Can be a list
385-
of arguments or a string (run via shell). The command
386-
must print a valid bearer token to stdout. When set,
387-
``token_file`` and OAuth credentials are not required.
387+
:param token_command: Fetch strategy for OAuth access token, same format as
388+
``.fetch`` params: ``["strategy", "arg1", ...]``.
389+
Supported strategies: ``command``, ``shell``.
390+
The command must print a valid bearer token to stdout.
391+
When set, ``token_file`` and OAuth credentials are not
392+
required.
388393

389394
.. storage:: google_contacts
390395

@@ -404,10 +409,12 @@ itself or write anything to it.
404409
:param client_id/client_secret: OAuth credentials, obtained from the Google
405410
API Manager. Optional if ``token_command``
406411
is set.
407-
:param token_command: Command to fetch an OAuth access token. Can be a list
408-
of arguments or a string (run via shell). The command
409-
must print a valid bearer token to stdout. When set,
410-
``token_file`` and OAuth credentials are not required.
412+
:param token_command: Fetch strategy for OAuth access token, same format as
413+
``.fetch`` params: ``["strategy", "arg1", ...]``.
414+
Supported strategies: ``command``, ``shell``.
415+
The command must print a valid bearer token to stdout.
416+
When set, ``token_file`` and OAuth credentials are not
417+
required.
411418

412419
The built-in OAuth flow is not ideal, but Google has deprecated the previous
413420
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
@@ -49,12 +49,7 @@ def __init__(
4949
connector: aiohttp.BaseConnector,
5050
):
5151
if token_command is not None:
52-
if isinstance(token_command, str):
53-
self._token_command = token_command
54-
self._token_command_shell = True
55-
else:
56-
self._token_command = list(token_command)
57-
self._token_command_shell = False
52+
self._token_command = list(token_command)
5853
else:
5954
self._token_command = None
6055
if not have_oauth2:
@@ -208,27 +203,23 @@ async def _init_token(self):
208203
await self._save_token(self._token)
209204

210205
async def _fetch_token_via_command(self):
211-
import subprocess
206+
from vdirsyncer.cli.fetchparams import STRATEGIES
212207

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

234225

0 commit comments

Comments
 (0)