Initial commit

This commit is contained in:
Kristof committed 2026-10-14 12:29:29 +02:00
commit 536ff43ce4
158 files changed
+30355

No files matched your search

+199
View File
@@ -0,0 +1,199 @@
"""
Gmail-delegatie: andere gebruikers toegang geven tot een mailbox.
Werkt via de Gmail API (users.settings.delegates). Het service-account handelt
in naam van de EIGENAAR van de mailbox (de delegator).
Vereist (eenmalig):
1. Admin-console › Beveiliging › API-beheer › Domeinbrede delegatie:
scope toevoegen aan het service-account:
https://www.googleapis.com/auth/gmail.settings.sharing
2. Admin-console › Apps › Google Workspace › Gmail › Gebruikersinstellingen:
'E-maildelegatie' inschakelen voor de betrokken organisatie-eenheden.
Beperkingen van Google:
- de gedelegeerde moet in dezelfde Google Workspace-organisatie zitten
- max. 25 gedelegeerden per mailbox, max. 10 mailboxen per gedelegeerde
- het kan tot 1 minuut duren voor de toegang actief is
"""
import sys
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from config import Config
from database import execute_query
from tenancy import google_credentials
# Toevoegen/verwijderen van gedelegeerden: gmail.settings.sharing.
# Opvragen (delegates.list/get): gmail.settings.basic (sharing volstaat daar NIET).
GMAIL_SCOPE = 'https://www.googleapis.com/auth/gmail.settings.sharing'
GMAIL_LEES_SCOPE = 'https://www.googleapis.com/auth/gmail.settings.basic'
MAX_DELEGATES = 25
STATUS_LABELS = {
'accepted': 'Actief',
'pending': 'In afwachting',
'rejected': 'Geweigerd',
'expired': 'Verlopen',
}
class DelegationError(Exception):
pass
class GmailDelegationService:
def __init__(self, directory_service=None):
# Bestaande Directory-client hergebruiken om adressen/aliassen op te zoeken
self._directory = directory_service
# ---------- clients ----------
def _gmail_for(self, mailbox, scope=GMAIL_SCOPE):
creds = google_credentials([scope], subject=mailbox)
try: # meegeleverde API-beschrijving: geen extra verzoek naar Google per mailbox
return build('gmail', 'v1', credentials=creds, static_discovery=True, cache_discovery=False)
except Exception:
return build('gmail', 'v1', credentials=creds, static_discovery=False, cache_discovery=False)
def _directory_api(self):
if self._directory is None:
from services.google_service import GoogleAdminService
self._directory = GoogleAdminService().service
return self._directory
# ---------- hulpfuncties ----------
@staticmethod
def _friendly_error(e, mailbox=None, delegate=None):
msg = str(e)
status = getattr(getattr(e, 'resp', None), 'status', None)
if 'unauthorized_client' in msg or 'access_denied' in msg or 'insufficient authentication scopes' in msg.lower():
return ("Google weigert de toegang: het serviceaccount mist een Gmail-recht. Voeg in admin.google.com › "
"Beveiliging › Toegangs- en gegevensbeheer › API-beheer › Domeinbrede delegatie bij het serviceaccount "
f"deze twee bereiken toe: {GMAIL_LEES_SCOPE} en {GMAIL_SCOPE}. Het kan enkele minuten duren voor dit werkt.")
if status == 409:
return f"{delegate} heeft al toegang tot {mailbox}."
laag = msg.lower()
reden = msg.split('returned', 1)[-1].strip(' ">')[:200] if 'returned' in msg else msg[:200]
if 'has not been used' in laag or 'is disabled' in laag or 'accessnotconfigured' in laag:
return ("De Gmail API staat nog niet aan in het Google Cloud-project van het serviceaccount "
"(console.cloud.google.com › API's en services › Bibliotheek › Gmail API › Inschakelen). "
f"Google: {reden}")
if 'mail service not enabled' in laag or 'failedprecondition' in laag or 'precondition' in laag:
return (f"{mailbox or 'Deze gebruiker'} heeft (nog) geen werkende Gmail-mailbox: controleer of Gmail aanstaat "
"en of de gebruiker een licentie heeft. Een net aangemaakt account kan tot 24 uur nodig hebben. "
f"Google: {reden}")
if 'delegation denied' in laag or 'not allowed' in laag or 'delegation is not allowed' in laag:
return ("Delegatie is niet toegestaan voor deze gebruiker. Controleer of 'E-maildelegatie' aanstaat "
"(Admin-console › Apps › Google Workspace › Gmail › Gebruikersinstellingen) voor de OU van de "
"mailbox én van de gedelegeerde. Een gewijzigde instelling kan tot 24 uur nodig hebben. "
f"Google: {reden}")
if status == 403:
return f"Google weigert deze actie (403). Google: {reden}"
if status == 400 and 'limit' in msg.lower():
return "Limiet bereikt: max. 25 gedelegeerden per mailbox en max. 10 mailboxen per gedelegeerde."
if status == 400:
return f"Google weigert deze delegatie: {getattr(e, 'reason', '') or msg}"
if status == 404:
return "Gebruiker of delegatie niet gevonden."
return msg
def resolve_user(self, email):
"""Zoekt de gebruiker op in Google en geeft het PRIMAIRE adres terug (aliassen werken niet bij delegatie)."""
email = (email or '').strip().lower()
if not email or '@' not in email:
raise DelegationError("Geef een volledig e-mailadres op.")
try:
u = self._directory_api().users().get(userKey=email).execute()
except HttpError as e:
if getattr(e.resp, 'status', None) == 404:
raise DelegationError(f"{email} bestaat niet in jullie Google Workspace. "
"Delegeren naar externe (bv. @gmail.com) adressen kan niet.")
raise DelegationError(self._friendly_error(e))
return {
'email': u['primaryEmail'].lower(),
'naam': (u.get('name') or {}).get('fullName', ''),
'ou': u.get('orgUnitPath', ''),
'geschorst': bool(u.get('suspended')),
'is_alias': u['primaryEmail'].lower() != email,
}
def search_users(self, query):
"""Gebruikers zoeken op naam of adres (voor de zoekbox)."""
q = query.strip()
try:
resp = self._directory_api().users().list(
customer='my_customer', query=q, maxResults=15, orderBy='email').execute()
except Exception as e:
sys.stderr.write(f"Zoeken mislukt: {e}\n")
return []
return [{'email': u['primaryEmail'], 'naam': (u.get('name') or {}).get('fullName', ''),
'ou': u.get('orgUnitPath', '')} for u in resp.get('users', [])]
# ---------- delegaties ----------
def list_delegates(self, mailbox):
try:
resp = self._gmail_for(mailbox, GMAIL_LEES_SCOPE).users().settings().delegates().list(userId='me').execute()
except HttpError as e:
raise DelegationError(self._friendly_error(e, mailbox))
except Exception as e:
raise DelegationError(self._friendly_error(e, mailbox))
result = []
for d in resp.get('delegates', []):
status = d.get('verificationStatus', '')
result.append({'email': d.get('delegateEmail', ''),
'status': status,
'status_label': STATUS_LABELS.get(status, status)})
result.sort(key=lambda d: d['email'])
return result
def add_delegate(self, mailbox, delegate):
if mailbox == delegate:
raise DelegationError("Een mailbox kan niet aan zichzelf gedelegeerd worden.")
try:
self._gmail_for(mailbox).users().settings().delegates().create(
userId='me', body={'delegateEmail': delegate}).execute()
except HttpError as e:
raise DelegationError(self._friendly_error(e, mailbox, delegate))
except Exception as e:
raise DelegationError(self._friendly_error(e, mailbox, delegate))
def remove_delegate(self, mailbox, delegate):
try:
self._gmail_for(mailbox).users().settings().delegates().delete(
userId='me', delegateEmail=delegate).execute()
except HttpError as e:
raise DelegationError(self._friendly_error(e, mailbox, delegate))
except Exception as e:
raise DelegationError(self._friendly_error(e, mailbox, delegate))
# ---------- logboek ----------
@staticmethod
def ensure_table():
execute_query("""
CREATE TABLE IF NOT EXISTS gmail_delegatie_log (
ID INT AUTO_INCREMENT PRIMARY KEY,
Tijdstip DATETIME NOT NULL,
Gebruiker VARCHAR(100) NULL,
Actie VARCHAR(20) NOT NULL,
Mailbox VARCHAR(255) NOT NULL,
Gedelegeerde VARCHAR(255) NOT NULL,
INDEX idx_mailbox (Mailbox),
INDEX idx_tijdstip (Tijdstip)
)""", commit=True)
@staticmethod
def log(gebruiker, actie, mailbox, gedelegeerde):
execute_query(
"INSERT INTO gmail_delegatie_log (Tijdstip, Gebruiker, Actie, Mailbox, Gedelegeerde) "
"VALUES (NOW(), %s, %s, %s, %s)",
(gebruiker, actie, mailbox, gedelegeerde), commit=True)
@staticmethod
def history(mailbox=None, limit=25):
if mailbox:
return execute_query(
"SELECT Tijdstip, Gebruiker, Actie, Mailbox, Gedelegeerde FROM gmail_delegatie_log "
"WHERE Mailbox = %s OR Gedelegeerde = %s ORDER BY Tijdstip DESC LIMIT %s",
(mailbox, mailbox, int(limit)))
return execute_query(
"SELECT Tijdstip, Gebruiker, Actie, Mailbox, Gedelegeerde FROM gmail_delegatie_log "
"ORDER BY Tijdstip DESC LIMIT %s", (int(limit),))