PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -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),))
|
||||
Reference in new issue
Block a user