Python e dizionari: gestire ruoli e permessi in modo flessibile

by theArchitect
SHARE
Python e dizionari: gestire ruoli e permessi in modo flessibile
© Guida-HTML5.it

Introduzione

I dizionari Python sono strutture dati ideali per rappresentare associazioni tra una chiave e un valore. Un caso pratico molto comune è la gestione dei ruoli utente e dei relativi permessi in un’applicazione.

Per esempio, un sistema potrebbe prevedere i ruoli admin, editor e viewer. Ogni ruolo permette di eseguire operazioni differenti: creare contenuti, modificare dati, visualizzare informazioni o gestire altri utenti.

Invece di scrivere molte condizioni if sparse nel programma, possiamo raccogliere le regole in un dizionario. In questo modo il codice diventa più leggibile, facile da modificare e semplice da estendere.

In questo tutorial costruiremo un piccolo sistema per:

  • definire i permessi associati a ogni ruolo;
  • verificare se un utente possiede un determinato permesso;
  • aggiungere e rimuovere utenti;
  • gestire ruoli inesistenti in modo sicuro;
  • produrre un riepilogo leggibile delle autorizzazioni.

Codice completo

# Dizionario che associa ogni ruolo all´insieme dei suoi permessi.
RUOLI = {
    "admin": {
        "visualizza_report",
        "modifica_articoli",
        "pubblica_articoli",
        "gestisci_utenti"
    },
    "editor": {
        "visualizza_report",
        "modifica_articoli",
        "pubblica_articoli"
    },
    "viewer": {
        "visualizza_report"
    }
}

# Dizionario degli utenti.
# La chiave è l´identificativo dell´utente,
# il valore è il suo ruolo.
UTENTI = {
    "anna": "admin",
    "marco": "editor",
    "lucia": "viewer"
}


def ruolo_esistente(ruolo):
    """Restituisce True se il ruolo è definito nel sistema."""
    return ruolo in RUOLI


def ha_permesso(username, permesso):
    """Verifica se un utente può eseguire una determinata operazione."""
    ruolo = UTENTI.get(username)

    # L´utente non esiste.
    if ruolo is None:
        return False

    # Il ruolo non è valido: configurazione incoerente.
    if ruolo not in RUOLI:
        return False

    return permesso in RUOLI[ruolo]


def aggiungi_utente(username, ruolo):
    """Aggiunge un utente solo se il ruolo è valido."""
    if not ruolo_esistente(ruolo):
        raise ValueError(f"Ruolo non valido: {ruolo}")

    UTENTI[username] = ruolo


def cambia_ruolo(username, nuovo_ruolo):
    """Modifica il ruolo di un utente esistente."""
    if username not in UTENTI:
        raise KeyError(f"Utente inesistente: {username}")

    if not ruolo_esistente(nuovo_ruolo):
        raise ValueError(f"Ruolo non valido: {nuovo_ruolo}")

    UTENTI[username] = nuovo_ruolo


def riepilogo_utente(username):
    """Restituisce una descrizione dei permessi dell´utente."""
    ruolo = UTENTI.get(username)

    if ruolo is None:
        return f"L´utente ´{username}´ non esiste."

    permessi = RUOLI.get(ruolo, set())

    if not permessi:
        return f"L´utente ´{username}´ ha un ruolo senza permessi."

    elenco = ", ".join(sorted(permessi))

    return (
        f"Utente: {username}n"
        f"Ruolo: {ruolo}n"
        f"Permessi: {elenco}"
    )


# Esempi di controllo dei permessi.
print(ha_permesso("anna", "gestisci_utenti"))
print(ha_permesso("marco", "gestisci_utenti"))
print(ha_permesso("lucia", "visualizza_report"))
print(ha_permesso("sconosciuto", "visualizza_report"))

# Aggiunta di un nuovo utente.
aggiungi_utente("paolo", "viewer")

# Modifica del ruolo di un utente.
cambia_ruolo("paolo", "editor")

# Stampa del riepilogo.
print(riepilogo_utente("paolo"))

Spiegazione

Un dizionario di dizionari logico

Nel programma utilizziamo due dizionari distinti. Il primo, RUOLI, collega il nome di un ruolo a un insieme di permessi:

RUOLI["editor"]

Il risultato è un set contenente le autorizzazioni dell’editor. Un insieme è particolarmente adatto perché un permesso non dovrebbe comparire più volte e il controllo di appartenenza è molto naturale:

"pubblica_articoli" in RUOLI["editor"]

Il secondo dizionario, UTENTI, collega invece il nome dell’utente al suo ruolo. Per ottenere il ruolo di un utente usiamo get():

ruolo = UTENTI.get("marco")

Se la chiave non esiste, get() restituisce None invece di sollevare un’eccezione KeyError. Questo comportamento è utile quando l’assenza dell’utente è una situazione prevista.

Perché usare una funzione di controllo

La funzione ha_permesso() nasconde i dettagli della struttura dati. Il resto dell’applicazione non deve sapere come sono organizzati i ruoli: deve soltanto chiedere se una certa operazione è consentita.

Questa separazione migliora la manutenzione. Se in futuro i permessi verranno caricati da un file JSON o da un database, sarà possibile modificare l’implementazione interna senza cambiare tutte le parti del programma che effettuano i controlli.

Aggiunta e modifica degli utenti

Le funzioni aggiungi_utente() e cambia_ruolo() verificano sempre che il ruolo esista. Senza questo controllo potremmo inserire dati incoerenti, ad esempio un utente con ruolo "super_editor" non definito in RUOLI.

Usiamo ValueError quando il valore fornito non è valido e KeyError quando si cerca di modificare un utente inesistente. Distinguere gli errori rende più semplice gestire correttamente i problemi nel codice chiamante.

Best practice

  • Preferire costanti descrittive: nomi come RUOLI e UTENTI rendono chiaro lo scopo dei dizionari.
  • Usare i set per i permessi: i set evitano duplicati e rendono leggibile il controllo con l’operatore in.
  • Validare i dati in ingresso: ogni nuovo ruolo deve essere controllato prima di essere assegnato.
  • Incapsulare la logica: funzioni come ha_permesso() evitano di ripetere l’accesso diretto ai dizionari.
  • Usare get() quando l’assenza è normale: è preferibile a un accesso diretto se la chiave potrebbe non esistere.
  • Non affidarsi soltanto al client: in un’applicazione web i permessi devono essere verificati anche sul server.
  • Separare dati e codice: in un progetto reale, ruoli e utenti potrebbero essere caricati da un database o da un file di configurazione.

È inoltre consigliabile evitare di modificare direttamente i dizionari da molte parti del programma. Centralizzare le operazioni in funzioni o classi riduce il rischio di introdurre dati non validi.

Riepilogo

I dizionari permettono di modellare in modo semplice ed efficace un sistema di ruoli e autorizzazioni. Un dizionario può associare un utente al proprio ruolo, mentre un secondo dizionario può collegare ogni ruolo all’insieme dei permessi disponibili.

Questa soluzione è adatta a prototipi, script amministrativi e applicazioni di piccole dimensioni. I concetti fondamentali da ricordare sono:

  • le chiavi dei dizionari devono identificare in modo univoco gli elementi;
  • get() aiuta a gestire le chiavi mancanti;
  • i set sono utili per rappresentare collezioni di autorizzazioni;
  • la validazione impedisce di creare configurazioni incoerenti;
  • le funzioni nascondono la complessità e migliorano la riusabilità.

Approfondisci con risorse ufficiali

SHARE