====== IfAuthEx Dokuwiki Plugin ======
> Plugin per dokuwiki che filtra il contenuto in base a nome utente e gruppi
> Guida all'uso sulla [[https://www.dokuwiki.org/plugin:ifauthex|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
* Sei loggato come utente
* Non sei loggato nella wiki
* Sei un utente sudo e quindi
{{ :progetti:5p4k:dickbutt.png?nolink&50 |}}
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.
* **Data inizio:** 8 gen. 2019
* **Data pubblicazione:** 11 gen. 2019
* **Stato:** completo (aggiornamenti possibili)
[[https://git.mittelab.org/proj/ifauthex-dokuwiki-plugin|Sorgente]]
[[https://www.dokuwiki.org/plugin:ifauthex|Pagina su DokuWiki]]
==== Reboot di Ifauth ====
Esiste già un plugin che fa questo, si chiama [[https://www.dokuwiki.org/plugin:ifauth|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 è [[https://en.wikipedia.org/wiki/Functional_completeness|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 [[https://github.com/splitbrain|Andreas Gohr]] e ad [[https://github.com/annda|annda]] che mi hanno [[https://github.com/mittelab/ifauthex-dokuwiki-plugin/pull/3|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 [[https://github.com/tanakakz-alpha|tanakakz-alpha]] per aver [[https://github.com/mittelab/ifauthex-dokuwiki-plugin/issues/1|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 [[https://newville.github.io/asteval/|Asteval]] di Python, che costruisce [[https://en.wikipedia.org/wiki/Abstract_syntax_tree|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 [[https://stackoverflow.com/a/380487/1749822|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 [[https://it.wikipedia.org/wiki/Ariet%C3%A0|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 AND NOT AND IN_GROUP CLOSE_PAR OR NOT OPEN_PAR IN_GROUP OR OR IN_GROUP 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:
] AND NOT [
Lit:
] AND IN_GROUP [
Lit:
] CLOSE_PAR OR NOT OPEN_PAR IN_GROUP [
Lit:
] OR [
Lit:
] OR IN_GROUP [
Lit:
] CLOSE_PAR
[
Subexpr: NOT IN_GROUP [
Lit:
] AND NOT [
Lit:
] AND IN_GROUP [
Lit:
]
] OR NOT [
Subexpr: IN_GROUP [
Lit:
] OR [
Lit:
] OR IN_GROUP [
Lit:
]
]
[
Subexpr: NOT [
InGroup: [
Lit:
]
] AND NOT [
Lit:
] AND [
InGroup: [
Lit:
]
]
] OR NOT [
Subexpr: [
InGroup: [
Lit:
]
] OR [
Lit:
] OR [
InGroup: [
Lit:
]
]
]
[
Subexpr: [
Not: [
InGroup: [
Lit:
]
]
] AND [
Not: [
Lit:
]
] AND [
InGroup: [
Lit:
]
]
] OR [
Not: [
Subexpr: [
InGroup: [
Lit:
]
] OR [
Lit:
] OR [
InGroup: [
Lit:
]
]
]
]
[
Or: [
Subexpr: [
And: [
Not: [
InGroup: [
Lit:
]
]
], [
Not: [
Lit:
]
], [
InGroup: [
Lit:
]
]
]
], [
Not: [
Subexpr: [
Or: [
InGroup: [
Lit:
]
], [
Lit:
], [
InGroup: [
Lit:
]
]
]
]
]
]
che corrisponde al seguente albero, una volta che le sottoespressioni vengono dissolte:
Or
+-- And:
| +-- Not:
| | +-- InGroup:
| | +-- Lit:
| +-- Not:
| | +-- Lit:
| +-- InGroup:
| +-- Lit:
+-- Not:
+-- Or:
+-- InGroup:
| +-- Lit:
+-- Lit:
+-- InGroup:
+-- Lit: