fix: resolve all 17 playbook findings (P0–P3)
P0 fixes: - agent register: upsert by agent_id (no duplicate rows) - daemon poll-once: extract _gmail_poll_cycle, run synchronously - garc_core.py: suppress urllib3/googleapiclient DeprecationWarnings P1 fixes: - OAuth: detect RefreshError → delete stale token → re-auth flow - OAuth: scope coverage check before returning valid creds - ingress: add stale-reset subcommand (reset in_progress > N min) - sheets: trim-sheet / clean-all — deleteDimension for empty rows - approval gate: send Gmail notification to GARC_APPROVAL_EMAIL P2 additions: - Google Chat: garc-chat-helper.py + garc send chat subcommands - Service Account: garc auth service-account verify + DWD support - Audit log: Sheets audit tab + garc audit list + bin/garc async hook - garc auth revoke: POST /revoke + delete token file - kg: pagination fix, shell injection fix, garc-kg-query.py - docs: _doc_insert_text / append_doc / garc drive append-doc P3 additions: - Multi-tenant: lib/profile.sh (list/use/add/show/remove/current) bin/garc: auto-load profile config.env and token.json - Google Forms pipeline: garc-forms-helper.py + lib/forms.sh garc forms list/responses/watch - systemd: _daemon_install_service OS-detect → launchd or systemd units - Python version gate (>=3.10) in bin/garc + pyproject.toml - garc doctor command for environment diagnostics Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
680bd433f4
commit
7b5951a1d5
21 changed files with 2078 additions and 144 deletions
|
|
@ -7,11 +7,19 @@ import json
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
import functools
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any
|
||||
|
||||
# Suppress noisy deprecation warnings from urllib3 / requests bundled
|
||||
# inside google-auth and google-api-python-client on Python 3.12+
|
||||
warnings.filterwarnings("ignore", message=".*urllib3.*", category=DeprecationWarning)
|
||||
warnings.filterwarnings("ignore", message=".*ssl.wrap_socket.*", category=DeprecationWarning)
|
||||
warnings.filterwarnings("ignore", message=".*imp module.*", category=DeprecationWarning)
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module="googleapiclient")
|
||||
|
||||
GARC_CONFIG_DIR = Path(os.environ.get("GARC_CONFIG_DIR", Path.home() / ".garc"))
|
||||
TOKEN_FILE = Path(os.environ.get("GARC_TOKEN_FILE", GARC_CONFIG_DIR / "token.json"))
|
||||
CREDENTIALS_FILE = Path(os.environ.get("GARC_CREDENTIALS_FILE", GARC_CONFIG_DIR / "credentials.json"))
|
||||
|
|
@ -82,21 +90,36 @@ def get_credentials(scopes: Optional[list] = None, use_service_account: bool = F
|
|||
try:
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google.auth.transport.requests import Request
|
||||
from google.auth.exceptions import RefreshError
|
||||
|
||||
creds = None
|
||||
if TOKEN_FILE.exists():
|
||||
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), scopes)
|
||||
|
||||
if creds and creds.valid:
|
||||
return creds
|
||||
# Verify the token covers the requested scopes
|
||||
if _scopes_covered(creds, scopes):
|
||||
return creds
|
||||
# Scope mismatch — need re-auth
|
||||
print("⚠️ Token scopes insufficient for requested operation.", file=sys.stderr)
|
||||
print(" Re-authenticating to add required scopes...", file=sys.stderr)
|
||||
creds = None
|
||||
|
||||
if creds and creds.expired and creds.refresh_token:
|
||||
try:
|
||||
creds.refresh(Request())
|
||||
_save_token(creds)
|
||||
return creds
|
||||
except RefreshError as e:
|
||||
# Token revoked or expired beyond refresh — delete and re-auth
|
||||
print(f"⚠️ Token refresh failed (revoked or expired): {e}", file=sys.stderr)
|
||||
print(" Deleting stale token. You will be prompted to log in again.", file=sys.stderr)
|
||||
TOKEN_FILE.unlink(missing_ok=True)
|
||||
creds = None
|
||||
except Exception as e:
|
||||
print(f"⚠️ Token refresh failed: {e}", file=sys.stderr)
|
||||
print(f"⚠️ Token refresh error: {e}", file=sys.stderr)
|
||||
TOKEN_FILE.unlink(missing_ok=True)
|
||||
creds = None
|
||||
|
||||
# Need fresh OAuth flow
|
||||
if not CREDENTIALS_FILE.exists():
|
||||
|
|
@ -117,6 +140,17 @@ def get_credentials(scopes: Optional[list] = None, use_service_account: bool = F
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def _scopes_covered(creds, requested_scopes: list) -> bool:
|
||||
"""Return True if the credential's granted scopes cover all requested scopes."""
|
||||
if not requested_scopes:
|
||||
return True
|
||||
granted = set(getattr(creds, "scopes", None) or [])
|
||||
if not granted:
|
||||
# Token file may not carry scope info — assume OK to avoid spurious re-auth
|
||||
return True
|
||||
return all(s in granted for s in requested_scopes)
|
||||
|
||||
|
||||
def _save_token(creds):
|
||||
"""Save credentials to token file."""
|
||||
GARC_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue