gerrit_util: add ReAuth support to ensure_authenticated

This CL makes it possible for ensure_authenticated to check whether
the current credential would satisfy ReAuth requirement.

Right now, only GitCredsAuthenticator performs additional ReAuth
check. For other Authenticator, either ReAuth is not relevant
(e.g. SSO), or not supported or implied (e.g. .gitcookies).

This CL does nothing on its own, because ensure_autheticated with
ReAuth is opt-in. Subsequent CL will update callsites to pass in
ReAuthContext to make the check effective.

Bug: 451651615
Change-Id: Idb1e70c52aaa844e672331fad8d462a2a7577d45
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/7104363
Commit-Queue: Jiewei Qian <qjw@chromium.org>
Reviewed-by: Scott Lee <ddoman@chromium.org>
Reviewed-by: Allen Li <ayatane@chromium.org>
This commit is contained in:
Jiewei Qian
2025-11-04 11:21:10 -08:00
committed by LUCI CQ
parent 1faea18c64
commit 5567ad1ea0
2 changed files with 124 additions and 12 deletions

View File

@@ -275,13 +275,24 @@ class _Authenticator(object):
"""
raise NotImplementedError()
def ensure_authenticated(self, *, gerrit_host: str,
git_host: str) -> Tuple[bool, str]:
def ensure_authenticated(
self,
*,
gerrit_host: str,
git_host: str,
reauth_context: Optional[auth.ReAuthContext] = None
) -> Tuple[bool, str]:
"""Returns (bypassable, error message).
If the error message is empty, there is no error to report.
If bypassable is true, the caller will allow the user to continue past the
error.
If `reauth_context` is provided, additionally checks whether the
authentication information satisfies the ReAuth requirement.
For the ReAuth check, Gerrit `host` attribute in `reauth_context` takes
precedence over `gerrit_host` argument.
"""
return (True, '')
@@ -346,15 +357,21 @@ def debug_auth() -> Tuple[str, str]:
return authn.__class__.__name__, authn.debug_summary_state()
def ensure_authenticated(*, gerrit_host: str,
git_host: str) -> Tuple[bool, str]:
def ensure_authenticated(
*,
gerrit_host: str,
git_host: str,
reauth_context: Optional[auth.ReAuthContext] = None
) -> Tuple[bool, str]:
"""Returns (bypassable, error message).
If the error message is empty, there is no error to report. If bypassable is
true, the caller will allow the user to continue past the error.
"""
return _Authenticator.get().ensure_authenticated(gerrit_host=gerrit_host,
git_host=git_host)
return _Authenticator.get().ensure_authenticated(
gerrit_host=gerrit_host,
git_host=git_host,
reauth_context=reauth_context)
class SSOAuthenticator(_Authenticator):
@@ -707,13 +724,24 @@ class CookiesAuthenticator(_Authenticator):
else:
conn.req_headers['Authorization'] = f'Bearer {cred}'
def ensure_authenticated(self, *, gerrit_host: str,
git_host: str) -> Tuple[bool, str]:
def ensure_authenticated(
self,
*,
gerrit_host: str,
git_host: str,
reauth_context: Optional[auth.ReAuthContext] = None
) -> Tuple[bool, str]:
"""Returns (bypassable, error message).
If the error message is empty, there is no error to report.
If bypassable is true, the caller will allow the user to continue past the
error.
Some bots are still using .gitcookies. They're marked as trusted robots,
and therefore already satisfies ReAuth requirement. So we don't need to
perform extra ReAuth checks here for bots.
Human users shouldn't be using .gitcookies by now.
"""
# Lazy-loader to identify Gerrit and Git hosts.
gerrit_auth = self._get_auth_for_host(gerrit_host)
@@ -974,6 +1002,24 @@ class GitCredsAuthenticator(_Authenticator):
return self.gerrit_account_exists(gerrit_host)
return False
def ensure_authenticated(
self,
*,
gerrit_host: str,
git_host: str,
reauth_context: Optional[auth.ReAuthContext] = None
) -> Tuple[bool, str]:
try:
if not reauth_context:
self._authenticator.get_access_token()
else:
self._authenticator.get_authorization_header(reauth_context)
return (True, '')
except (auth.GitLoginRequiredError, auth.GitReAuthRequiredError,
auth.GitUnknownError) as e:
return (False, str(e))
class NoAuthenticator(_Authenticator):
"""_Authenticator implementation that does no auth.
@@ -1031,11 +1077,24 @@ class ChainedAuthenticator(_Authenticator):
def debug_summary_state(self) -> str:
return ''
def ensure_authenticated(self, *, gerrit_host: str,
git_host: str) -> Tuple[bool, str]:
def ensure_authenticated(
self,
*,
gerrit_host: str,
git_host: str,
reauth_context: Optional[auth.ReAuthContext] = None
) -> Tuple[bool, str]:
for a in self.applicable_authenticators(gerrit_host=gerrit_host):
return a.ensure_authenticated(gerrit_host=gerrit_host,
git_host=git_host)
bypassable, err_msg = a.ensure_authenticated(
gerrit_host=gerrit_host,
git_host=git_host,
reauth_context=reauth_context)
if err_msg:
logging.info(
"%s.ensure_authenticated() returns an %s error: %s",
a.__class__.__name__,
"a bypassable" if bypassable else "an", err_msg)
return bypassable, err_msg
return (False,
f'{self!r} has no applicable authenticator for {gerrit_host}')