PIVOT 1.1.0
This commit is contained in:
commit
8a53234eb6
161 files changed
+31109
No files matched your search
@@ -0,0 +1,280 @@
|
||||
"""
|
||||
Google › Gedeelde drives: alle gedeelde drives van het domein, wie er rechten op heeft, en leden beheren.
|
||||
|
||||
Google Drive API v3 namens de beheerder (domeinbrede delegatie), met useDomainAdminAccess=True zodat ook drives
|
||||
zichtbaar zijn waar de beheerder zelf geen lid van is:
|
||||
drives.list / drives.create
|
||||
permissions.list / create / update / delete (supportsAllDrives=True)
|
||||
Vereist: https://www.googleapis.com/auth/drive (en de Google Drive API aan in het Cloud-project).
|
||||
"""
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
|
||||
from config import Config
|
||||
from tenancy import google_credentials, huidige_school, in_school
|
||||
|
||||
SCOPE = 'https://www.googleapis.com/auth/drive'
|
||||
CACHE_SECONDEN = 10 * 60
|
||||
ROLLEN = [('organizer', 'Beheerder'), ('fileOrganizer', 'Inhoudsbeheerder'), ('writer', 'Bijdrager'),
|
||||
('commenter', 'Reageerder'), ('reader', 'Lezer')]
|
||||
ROL_NAAM = dict(ROLLEN)
|
||||
PERM_VELDEN = 'nextPageToken,permissions(id,type,role,emailAddress,domain,displayName,deleted)'
|
||||
|
||||
|
||||
class DriveFout(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def vriendelijk(e):
|
||||
t = str(e)
|
||||
laag = t.lower()
|
||||
if 'unauthorized_client' in laag or 'access_denied' in laag:
|
||||
return ("Google weigert de toegang: voeg in de Admin-console › Beveiliging › API-beheer › Domeinbrede delegatie "
|
||||
f"het bereik {SCOPE} toe bij het serviceaccount van PIVOT.")
|
||||
if 'has not been used' in laag or 'is disabled' in laag or 'accessnotconfigured' in laag:
|
||||
return "De Google Drive API staat niet aan in het Google Cloud-project van het serviceaccount."
|
||||
if 'insufficientadministratorprivileges' in laag or 'domain administrator' in laag:
|
||||
return "De beheerder (Koppelingen › Algemeen) heeft geen beheerdersrechten voor Drive in het Admin-console."
|
||||
if 'cannotremovelastorganizer' in laag or 'last organizer' in laag:
|
||||
return "Dit is de laatste beheerder van de drive; voeg eerst een andere beheerder toe."
|
||||
if 'invalidsharingrequest' in laag or 'shareoutnotpermitted' in laag or 'sharingrateLimitExceeded'.lower() in laag:
|
||||
return "Google laat dit delen niet toe (bv. delen buiten het domein staat uit voor deze drive of organisatie)."
|
||||
if 'notfound' in laag.replace(' ', '') or ' 404' in t:
|
||||
return "Niet gevonden (bestaat de drive, de gebruiker of de groep nog?)."
|
||||
return t[:300]
|
||||
|
||||
|
||||
def _drive():
|
||||
from googleapiclient.discovery import build
|
||||
creds = google_credentials([SCOPE], subject=Config.GOOGLE_ADMIN_EMAIL)
|
||||
try:
|
||||
return build('drive', 'v3', credentials=creds, static_discovery=True, cache_discovery=False)
|
||||
except Exception:
|
||||
return build('drive', 'v3', credentials=creds, static_discovery=False, cache_discovery=False)
|
||||
|
||||
|
||||
def _eigen_domeinen():
|
||||
s = huidige_school()
|
||||
d = {x.lower() for x in (getattr(s, 'domeinen', None) or [s.domein]) if x}
|
||||
if Config.GOOGLE_ADMIN_EMAIL:
|
||||
d.add(Config.GOOGLE_ADMIN_EMAIL.lower().partition('@')[2])
|
||||
return d
|
||||
|
||||
|
||||
def _is_extern(p, domeinen):
|
||||
if p.get('type') == 'anyone':
|
||||
return True
|
||||
if p.get('type') == 'domain':
|
||||
return (p.get('domain') or '').lower() not in domeinen
|
||||
mail = (p.get('emailAddress') or '').lower()
|
||||
return bool(mail) and mail.partition('@')[2] not in domeinen
|
||||
|
||||
|
||||
def _leden(svc, drive_id):
|
||||
uit, token = [], None
|
||||
while True:
|
||||
resp = svc.permissions().list(fileId=drive_id, supportsAllDrives=True, useDomainAdminAccess=True,
|
||||
pageSize=100, pageToken=token, fields=PERM_VELDEN).execute()
|
||||
uit += resp.get('permissions', [])
|
||||
token = resp.get('nextPageToken')
|
||||
if not token:
|
||||
return uit
|
||||
|
||||
|
||||
def _samenvatting(d, leden, domeinen):
|
||||
return {'id': d['id'], 'naam': d.get('name', ''), 'aangemaakt': (d.get('createdTime') or '')[:10],
|
||||
'verborgen': bool(d.get('hidden')), 'leden': len(leden),
|
||||
'beheerders': sum(1 for p in leden if p.get('role') == 'organizer'),
|
||||
'extern': sum(1 for p in leden if _is_extern(p, domeinen)),
|
||||
'groepen': sum(1 for p in leden if p.get('type') == 'group'),
|
||||
'fout': None}
|
||||
|
||||
|
||||
def overzicht(vernieuw=False):
|
||||
"""[{id, naam, aangemaakt, leden, beheerders, extern, groepen, verborgen, fout}] gesorteerd op naam."""
|
||||
school = huidige_school()
|
||||
bewaard = school.cache.get('gedeelde_drives')
|
||||
if bewaard and not vernieuw and time.time() - bewaard[0] < CACHE_SECONDEN:
|
||||
return bewaard[1], bewaard[0]
|
||||
try:
|
||||
svc = _drive()
|
||||
drives, token = [], None
|
||||
while True:
|
||||
resp = svc.drives().list(useDomainAdminAccess=True, pageSize=100, pageToken=token,
|
||||
fields='nextPageToken,drives(id,name,createdTime,hidden)').execute()
|
||||
drives += resp.get('drives', [])
|
||||
token = resp.get('nextPageToken')
|
||||
if not token:
|
||||
break
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
domeinen = _eigen_domeinen()
|
||||
lokaal = threading.local()
|
||||
alle_leden = {}
|
||||
|
||||
def info(d):
|
||||
with in_school(school):
|
||||
if not hasattr(lokaal, 'svc'):
|
||||
lokaal.svc = _drive()
|
||||
try:
|
||||
leden = _leden(lokaal.svc, d['id'])
|
||||
alle_leden[d['id']] = leden
|
||||
return _samenvatting(d, leden, domeinen)
|
||||
except Exception as e:
|
||||
r = _samenvatting(d, [], domeinen)
|
||||
r['fout'] = vriendelijk(e)
|
||||
return r
|
||||
|
||||
with ThreadPoolExecutor(max_workers=6) as pool:
|
||||
uit = list(pool.map(info, drives))
|
||||
uit.sort(key=lambda d: d['naam'].lower())
|
||||
nu = time.time()
|
||||
school.cache['gedeelde_drives'] = (nu, uit)
|
||||
school.cache['gedeelde_drives_leden'] = alle_leden
|
||||
return uit, nu
|
||||
|
||||
|
||||
def _vergeet():
|
||||
huidige_school().cache.pop('gedeelde_drives', None)
|
||||
|
||||
|
||||
def drive(drive_id):
|
||||
"""(drive-info, leden) van één drive. Leden gesorteerd: beheerders eerst, dan op naam."""
|
||||
svc = _drive()
|
||||
try:
|
||||
d = svc.drives().get(driveId=drive_id, useDomainAdminAccess=True, fields='id,name,createdTime,hidden').execute()
|
||||
leden = _leden(svc, drive_id)
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
domeinen = _eigen_domeinen()
|
||||
volgorde = {r: i for i, (r, _) in enumerate(ROLLEN)}
|
||||
rijen = [{'id': p['id'], 'type': p.get('type'), 'rol': p.get('role'), 'rol_naam': ROL_NAAM.get(p.get('role'), p.get('role')),
|
||||
'email': p.get('emailAddress') or (p.get('domain') if p.get('type') == 'domain' else ''),
|
||||
'naam': p.get('displayName') or '', 'extern': _is_extern(p, domeinen), 'verwijderd': bool(p.get('deleted'))}
|
||||
for p in leden]
|
||||
rijen.sort(key=lambda r: (volgorde.get(r['rol'], 9), (r['naam'] or r['email']).lower()))
|
||||
return _samenvatting(d, leden, domeinen), rijen
|
||||
|
||||
|
||||
def _soort(email):
|
||||
"""'group' als het adres een Google-groep is, anders 'user'."""
|
||||
try:
|
||||
from googleapiclient.discovery import build
|
||||
creds = google_credentials(Config.GOOGLE_API_SCOPES, subject=Config.GOOGLE_ADMIN_EMAIL)
|
||||
build('admin', 'directory_v1', credentials=creds, static_discovery=False, cache_discovery=False) \
|
||||
.groups().get(groupKey=email, fields='id').execute()
|
||||
return 'group'
|
||||
except Exception:
|
||||
return 'user'
|
||||
|
||||
|
||||
def lid_toevoegen(drive_id, email, rol, melding=False):
|
||||
email = (email or '').strip().lower()
|
||||
if '@' not in email:
|
||||
raise DriveFout("Geef een geldig e-mailadres (persoon of groep).")
|
||||
if rol not in ROL_NAAM:
|
||||
raise DriveFout("Onbekende rol.")
|
||||
try:
|
||||
_drive().permissions().create(fileId=drive_id, supportsAllDrives=True, useDomainAdminAccess=True,
|
||||
sendNotificationEmail=bool(melding), fields='id',
|
||||
body={'type': _soort(email), 'role': rol, 'emailAddress': email}).execute()
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
_vergeet()
|
||||
|
||||
|
||||
def lid_wijzigen(drive_id, perm_id, rol):
|
||||
if rol not in ROL_NAAM:
|
||||
raise DriveFout("Onbekende rol.")
|
||||
try:
|
||||
_drive().permissions().update(fileId=drive_id, permissionId=perm_id, supportsAllDrives=True,
|
||||
useDomainAdminAccess=True, body={'role': rol}, fields='id').execute()
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
_vergeet()
|
||||
|
||||
|
||||
def lid_verwijderen(drive_id, perm_id):
|
||||
try:
|
||||
_drive().permissions().delete(fileId=drive_id, permissionId=perm_id, supportsAllDrives=True,
|
||||
useDomainAdminAccess=True).execute()
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
_vergeet()
|
||||
|
||||
|
||||
def drive_maken(naam, beheerder=None):
|
||||
"""Nieuwe gedeelde drive (de PIVOT-beheerder wordt eerst beheerder); optioneel meteen een andere beheerder."""
|
||||
naam = (naam or '').strip()
|
||||
if not naam:
|
||||
raise DriveFout("Geef de drive een naam.")
|
||||
try:
|
||||
d = _drive().drives().create(requestId=str(uuid.uuid4()), body={'name': naam[:100]}, fields='id,name').execute()
|
||||
except Exception as e:
|
||||
raise DriveFout(vriendelijk(e))
|
||||
_vergeet()
|
||||
if beheerder:
|
||||
lid_toevoegen(d['id'], beheerder, 'organizer')
|
||||
return d['id']
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Een gebruiker op alle gedeelde drives vervangen door een andere
|
||||
# ---------------------------------------------------------------------
|
||||
def drives_van(email, vernieuw=True):
|
||||
"""Alle gedeelde drives waar `email` lid van is: [{drive, naam, perm, rol, rol_naam}] (vers opgehaald)."""
|
||||
email = (email or '').strip().lower()
|
||||
drives, _ = overzicht(vernieuw=vernieuw)
|
||||
leden = huidige_school().cache.get('gedeelde_drives_leden') or {}
|
||||
uit = []
|
||||
for d in drives:
|
||||
for p in leden.get(d['id'], []):
|
||||
adres = (p.get('emailAddress') or '').lower()
|
||||
# Met een @: exact dat adres; zonder @: een deel van het adres of de naam (bv. 'peeters')
|
||||
if adres == email or ('@' not in email and email and
|
||||
(email in adres or email in (p.get('displayName') or '').lower())):
|
||||
uit.append({'drive': d['id'], 'naam': d['naam'], 'perm': p['id'], 'rol': p.get('role'),
|
||||
'rol_naam': ROL_NAAM.get(p.get('role'), p.get('role')), 'email': adres,
|
||||
'wie': p.get('displayName') or adres})
|
||||
return uit
|
||||
|
||||
|
||||
def vervang(oud, nieuw, drive_ids, oud_verwijderen=True, melding=False):
|
||||
"""Op elke gekozen drive: `nieuw` krijgt exact de rol van `oud` (ook als hij al lid was), daarna wordt `oud`
|
||||
verwijderd (optioneel). Geeft [{naam, ok, melding}]."""
|
||||
oud, nieuw = (oud or '').strip().lower(), (nieuw or '').strip().lower()
|
||||
if '@' not in nieuw:
|
||||
raise DriveFout("Geef een geldig e-mailadres voor de nieuwe gebruiker.")
|
||||
if oud == nieuw:
|
||||
raise DriveFout("De oude en de nieuwe gebruiker zijn dezelfde.")
|
||||
svc, soort, uit = _drive(), _soort(nieuw), []
|
||||
kies = set(drive_ids)
|
||||
for t in drives_van(oud, vernieuw=False):
|
||||
if t['drive'] not in kies:
|
||||
continue
|
||||
try:
|
||||
leden = _leden(svc, t['drive'])
|
||||
bestaand = next((p for p in leden if (p.get('emailAddress') or '').lower() == nieuw), None)
|
||||
if bestaand is None:
|
||||
svc.permissions().create(fileId=t['drive'], supportsAllDrives=True, useDomainAdminAccess=True,
|
||||
sendNotificationEmail=bool(melding), fields='id',
|
||||
body={'type': soort, 'role': t['rol'], 'emailAddress': nieuw}).execute()
|
||||
tekst = f"{nieuw} toegevoegd als {t['rol_naam']}"
|
||||
elif bestaand.get('role') != t['rol']: # altijd exact dezelfde rechten als de oude gebruiker
|
||||
svc.permissions().update(fileId=t['drive'], permissionId=bestaand['id'], supportsAllDrives=True,
|
||||
useDomainAdminAccess=True, body={'role': t['rol']}, fields='id').execute()
|
||||
tekst = f"{nieuw} was al lid, nu {t['rol_naam']}"
|
||||
else:
|
||||
tekst = f"{nieuw} was al lid ({ROL_NAAM.get(bestaand.get('role'))})"
|
||||
if oud_verwijderen:
|
||||
svc.permissions().delete(fileId=t['drive'], permissionId=t['perm'], supportsAllDrives=True,
|
||||
useDomainAdminAccess=True).execute()
|
||||
tekst += f", {oud} verwijderd"
|
||||
uit.append({'naam': t['naam'], 'ok': True, 'melding': tekst})
|
||||
except Exception as e:
|
||||
uit.append({'naam': t['naam'], 'ok': False, 'melding': vriendelijk(e)})
|
||||
_vergeet()
|
||||
return uit
|
||||
Reference in new issue
Block a user