mirror of
https://github.com/bellingcat/auto-archiver-api.git
synced 2026-06-08 03:28:35 +03:00
146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
"""Utilities for checking Google Sheet access permissions."""
|
|
|
|
from functools import lru_cache
|
|
|
|
import requests as http_requests
|
|
import yaml
|
|
|
|
from app.shared.log import logger
|
|
|
|
|
|
@lru_cache(maxsize=32)
|
|
def get_service_account_json_path(orchestrator_sheet_path: str) -> str | None:
|
|
"""
|
|
Extract the service account JSON file path from an orchestrator sheet
|
|
YAML config.
|
|
|
|
Returns:
|
|
Path to the service account JSON file, or None if not found.
|
|
"""
|
|
if not orchestrator_sheet_path:
|
|
return None
|
|
|
|
try:
|
|
with open(orchestrator_sheet_path) as f:
|
|
orch = yaml.safe_load(f)
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Could not read orchestrator sheet config {orchestrator_sheet_path}: {e}"
|
|
)
|
|
return None
|
|
|
|
if not isinstance(orch, dict):
|
|
return None
|
|
|
|
def find_key(d: dict, key: str):
|
|
for k, v in d.items():
|
|
if k == key:
|
|
return v
|
|
if isinstance(v, dict):
|
|
if result := find_key(v, key):
|
|
return result
|
|
return None
|
|
|
|
return find_key(orch, "service_account")
|
|
|
|
|
|
def check_sheet_write_access(
|
|
service_account_json_path: str, sheet_id: str
|
|
) -> bool | None:
|
|
"""
|
|
Check if a Google service account has write (Editor) access to a Google
|
|
Sheet using the Google Drive API.
|
|
|
|
Returns:
|
|
True: Service account has write access.
|
|
False: Service account does NOT have write access (or no access).
|
|
None: Could not determine (network/auth error) — caller should
|
|
proceed with archiving and let it fail naturally.
|
|
"""
|
|
try:
|
|
from google.auth.transport.requests import Request
|
|
from google.oauth2.service_account import Credentials
|
|
|
|
creds = Credentials.from_service_account_file(
|
|
service_account_json_path,
|
|
scopes=["https://www.googleapis.com/auth/drive.metadata.readonly"],
|
|
)
|
|
creds.refresh(Request())
|
|
|
|
resp = http_requests.get(
|
|
f"https://www.googleapis.com/drive/v3/files/{sheet_id}",
|
|
params={
|
|
"fields": "capabilities/canEdit",
|
|
"supportsAllDrives": "true",
|
|
},
|
|
headers={"Authorization": f"Bearer {creds.token}"},
|
|
timeout=10,
|
|
)
|
|
|
|
if resp.status_code == 404:
|
|
return False
|
|
if resp.status_code == 403:
|
|
# Distinguish "API not enabled" from "no access to sheet"
|
|
try:
|
|
error_details = resp.json().get("error", {})
|
|
for detail in error_details.get("details", []):
|
|
if detail.get("reason") == "SERVICE_DISABLED":
|
|
logger.warning(
|
|
f"Google Drive API is not enabled for this project."
|
|
f"Contact an admin to enable it at: {detail.get('metadata', {}).get('activationUrl', 'N/A')}"
|
|
)
|
|
return None
|
|
except Exception:
|
|
pass
|
|
return False
|
|
if resp.status_code == 200:
|
|
return resp.json().get("capabilities", {}).get("canEdit", False)
|
|
|
|
logger.warning(
|
|
f"Unexpected Drive API response {resp.status_code} for sheet {sheet_id}: {resp.text}"
|
|
)
|
|
return None
|
|
|
|
except FileNotFoundError:
|
|
logger.error(
|
|
f"Service account JSON not found: {service_account_json_path}"
|
|
)
|
|
return None
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"Could not check write access for sheet {sheet_id}: {e}"
|
|
)
|
|
return None
|
|
|
|
|
|
def get_sheet_access_error(
|
|
orchestrator_sheet_path: str | None,
|
|
service_account_email: str | None,
|
|
sheet_id: str,
|
|
) -> str | None:
|
|
"""
|
|
Check if the service account has write access to a Google Sheet.
|
|
|
|
Returns:
|
|
An error message string if the sheet is NOT accessible, or None if
|
|
access is OK (or the check is indeterminate).
|
|
"""
|
|
if not orchestrator_sheet_path:
|
|
return None
|
|
|
|
sa_json_path = get_service_account_json_path(orchestrator_sheet_path)
|
|
if not sa_json_path:
|
|
return None
|
|
|
|
has_access = check_sheet_write_access(sa_json_path, sheet_id)
|
|
if has_access is False:
|
|
sa_display = (
|
|
service_account_email or "the Auto Archiver service account"
|
|
)
|
|
return (
|
|
f"The Google Sheet has not been shared with the Auto Archiver "
|
|
f"service account ({sa_display}). Please share the sheet with "
|
|
f"this email address and give it Editor permissions."
|
|
)
|
|
return None
|