Indice

IfAuthEx Dokuwiki Plugin

Plugin per dokuwiki che filtra il contenuto in base a nome utente e gruppi
Guida all'uso sulla Wiki di Dokuwiki.
Questo plugin per Dokuwiki attiva e disattiva alcune zone di una pagina wiki in base al nome utente di chi la sta visualizzando. La pagina per ovvie ragioni non può essere in cache, ma permette di realizzare effetti interessanti, come ad esempio personalizzare la landing page della wiki.

È utilizzato anche in questa stessa wiki, vediamolo in azione. In questo momento

  • Non sei loggato nella wiki

Il messaggio qui sopra cambia a seconda del login dell'utente. Ovviamente si può sempre rivelare guardando il sorgente della pagina. Lo scopo non è tenerla segreta, solo personalizzarla.

IfAuthEx Dokuwiki plugin

  • Data inizio: 8 gen. 2019
  • Data pubblicazione: 11 gen. 2019
  • Stato: completo (aggiornamenti possibili)

Sorgente

Pagina su DokuWiki

Reboot di Ifauth

Esiste già un plugin che fa questo, si chiama IfAuth. Tuttavia permette soltanto di combinare condizioni (e.g. nome utente o gruppo di appartenenza, potenzialmente negati) con l'operatore OR, senza associatività, e quindi non è funzionalmente completo (cioè non puoi esprimere condizioni tipo “gruppo A e anche gruppo B”).

Ifauth è un plugin molto semplice, riadattarlo perché fosse funzionalmente completo sarebbe stato equivalente a riscriverlo, per cui ho deciso di riscriverlo.

Sviluppi recenti e ringraziamenti

Tra le ultime modifiche, è stato riparata la grammatica per supportare username con dash e dots, aggiunto il supporto per stringhe multibyte, e risistemato l'output perché non spezzi le sezioni di DokuWiki.

Un grazie va ad Andreas Gohr e ad annda che mi hanno aiutato a rendere più robusto l'output hanno ripulito la parte specifica a DokuWiki. Un doppio grazie ad Andreas per tutto il suo lavoro al progetto DokuWiki. Grazie anche a tanakakz-alpha per aver sollevato la questione delle stringhe multibyte e a Matthias Keller per aver individuato la necessità di estendere i literals.

Note di implementazione

Abstract syntax tree

Una possibilità era creare una sintassi semplice, concatenando espressioni letterali (utente o @gruppo, potenzialmente negati) con gli operatori logici AND e OR, ma personalmente non mi ricordo mai quale dei due ha la priorità sull'altro, quindi dare la possibilità di usare le parentesi era necessario.

Espressioni letterali, AND, OR, NOT e parentesi possono costruire espressioni booleane arbitrarie, per cui l'ideale sarebbe stato avere qualcosa di pratico come la libreria Asteval di Python, che costruisce AST della sintassi di Python stesso e permette di valutarli in un contesto pressoché sicuro. Tuttavia non ho trovato niente del genere per PHP, per cui l'ho implementato direttamente.

Per ottenere un AST da un'espressione arbitraria, sono necessari tokenizer, lexer e parser.

Tokenizer

Il tokenizer è implementato utilizzando espressioni regolari (che è un po' eccessivo e sicuramente non performante, ma decisamente pratico). Consuma caratteri dall'espressione e identifica i vari token specificati nella lista dei token supportati. È in grado di ignorare token come gli spazi, e ricorda la posizione identificata nella stringa originale per dare messaggi di errore comprensibili.

Lexer

Il lexer scannerizza in ordine una grammatica definita direttamente nel plugin, e supporta operatori di diverso tipo: infix, prefix, postfix, wrap (non proprio un fixing ma serve per le parentesi), e nessuno (per le espressioni letterali), con delle limitazioni nel numero di token che è in grado di identificare per ciascun tipo di operatore. Ogni operatore ammette un certo rango, con delle limitazioni.

Ad esempio, l'operatore “OR” ha un solo token, ||, ed è un operatore infix con arietà infinita, cioè è in grado di identificare espressioni del tipo x0 || x1 || … || xn. Le limitazioni sul numero di token e sul rango servono fondamentalmente per avere un pattern matching semplificato corrispondente al tipo di fixing dell'operatore: intercalato tra altre espressioni per prefix/postfix/infix (con l'opportuno shift di uno se si tratta di prefix e postfix), oppure intorno alle espressioni (le parentesi). Fondamentalmente, si tratta di trovare tutti i token di un certo operatore, intercalati opportunamente ad altre espressioni. Processando gli operatori in ordine di priorità, si garantisce un'espressione ben formata (oppure si è in grado di lanciare un'eccezione se ad esempio c'è il token ( ma non il corrispondente )).

Parser

Il parser e il lexer sono integrati in uno in quest'implementazione. Non appena un operatore viene identificato, i token rilevanti per quell'operatore vengono raggruppati e sostituiti con un'istanza di tale operatore. I raggruppamenti codificano l'AST: si procede ad identificare l'operatore successivo, partendo dalla radice dell'albero e scendendo in tutti gli operatori precedentemente identificati. Il risultato è direttamente un AST.

L'AST ha una funzionalità per ricostruire l'espressione originale (a meno di spazi) che viene utilizzata nell'unit test.

Contesto di valutazione

L'AST può venir valutato direttamente, basta attraversarlo. Le informazioni relative all'utente e ai suoi gruppi però sono racchiuse in una classe EvaluationContext, per poter simulare utenti e gruppi in unit test.

Esempio di funzionamento

L'espressione è

(!@A && !B && @C) || !(@D || E || @F)

Il tokenizer la scompone in

OPEN_PAR NOT IN_GROUP <A> AND NOT <B> AND IN_GROUP <C> CLOSE_PAR OR NOT OPEN_PAR IN_GROUP <D> OR <E> OR IN_GROUP <F> CLOSE_PAR

Il lexer/parser processa in ordine espressioni letterali, sottoespressioni, operatore di test del gruppo, NOT, AND, OR, quindi in ordine trasforma l'espressione come segue:

OPEN_PAR NOT IN_GROUP [
    Lit: <A>
] AND NOT [
    Lit: <B>
] AND IN_GROUP [
    Lit: <C>
] CLOSE_PAR OR NOT OPEN_PAR IN_GROUP [
    Lit: <D>
] OR [
    Lit: <E>
] OR IN_GROUP [
    Lit: <F>
] CLOSE_PAR
[
    Subexpr: NOT IN_GROUP [
        Lit: <A>
    ] AND NOT [
        Lit: <B>
    ] AND IN_GROUP [
        Lit: <C>
    ]
] OR NOT [
    Subexpr: IN_GROUP [
        Lit: <D>
    ] OR [
        Lit: <E>
    ] OR IN_GROUP [
        Lit: <F>
    ]
]
[
    Subexpr: NOT [
        InGroup: [
            Lit: <A>
        ]
    ] AND NOT [
        Lit: <B>
    ] AND [
        InGroup: [
            Lit: <C>
        ]
    ]
] OR NOT [
    Subexpr: [
        InGroup: [
            Lit: <D>
        ]
    ] OR [
        Lit: <E>
    ] OR [
        InGroup: [
            Lit: <F>
        ]
    ]
]
[
    Subexpr: [
        Not: [
            InGroup: [
                Lit: <A>
            ]
        ]
    ] AND [
        Not: [
            Lit: <B>
        ]
    ] AND [
        InGroup: [
            Lit: <C>
        ]
    ]
] OR [
    Not: [
        Subexpr: [
            InGroup: [
                Lit: <D>
            ]
        ] OR [
            Lit: <E>
        ] OR [
            InGroup: [
                Lit: <F>
            ]
        ]
    ]
]
[
    Or:  [
        Subexpr: [
            And: [
                Not: [
                    InGroup: [
                        Lit: <A>
                    ]
                ]
            ], [
                Not: [
                    Lit: <B>
                ]
            ], [
                InGroup: [
                    Lit: <C>
                ]
            ]
        ]
    ], [
        Not: [
            Subexpr: [
                Or: [
                    InGroup: [
                        Lit: <D>
                    ]
                ], [
                    Lit: <E>
                ], [
                    InGroup: [
                        Lit: <F>
                    ]
                ]
            ]
        ]
    ]
]

che corrisponde al seguente albero, una volta che le sottoespressioni vengono dissolte:

Or
+-- And:
|   +-- Not:
|   |   +-- InGroup:
|   |       +-- Lit: <A>
|   +-- Not:
|   |   +-- Lit: <B>
|   +-- InGroup:
|       +-- Lit: <C>
+-- Not:
    +-- Or:
        +-- InGroup:
        |   +-- Lit: <D>
        +-- Lit: <E>
        +-- InGroup:
            +-- Lit: <F>