Molte applicazioni sono pensate per essere utilizzate da utenti con diversi ruoli applicativi. In questi casi è necessario poter riconfigurare l’interfaccia dell’applicazione in modo da adattarsi ai ruoli attivi in una determinata sessione, ad esempio nascondendo le funzioni non disponibili.
L’implementazione di queste specifiche normalmente non è semplice. Innanzitutto di frequente esse nascono o vengono modificate quando l’applicazione è già stata implementata, quindi si tratta di modificare un codice già testato e funzionante. C’è un fattore in più: il codice che modifica lo stato dell’interfaccia utente in base al ruolo potrebbe interagire con altro codice già scritto che ne gestisce lo stato in maniera autonoma. Si tratta quindi di due fattori diversi che si trovano in conflitto sull’uso delle medesime risorse.
Per spiegare meglio facciamo un esempio: il pulsante Inizia modifica di una videata di dettaglio deve essere gestito a livello di stato della videata, cioè se il documento mostrato è già in fase di modifica il pulsante non deve apparire, altrimenti sì. Allo stesso tempo, la visibilità dello stesso pulsante potrebbe essere controllata da un sistema di configurazione che ne vieta l’utilizzo ad un determinato tipo di utenti.
Per gestire correttamente questa situazione è necessario che tutti i punti in cui viene gestita la visibilità del pulsante vengano centralizzati e che si tenga conto anche delle specifiche relative al ruolo dell’utente. Questo può comportare un refactoring piuttosto costoso, tenendo anche conto della necessità di aggiornare il sistema di test dell’applicazione.
Un ulteriore fattore di complicazione risiede nel fatto che le specifiche relative ai ruoli molto spesso sono ricche di eccezioni, magari legate ad uno specifico utente. Per ogni singola modifica si tratta quindi di modificare il codice dell’applicazione in una parte critica: un errore di programmazione potrebbe esporre funzioni ad utenti che non dovrebbero utilizzarle e non è facile avere la certezza che questo non avvenga.
Il foglio di controllo accessi #
Il framework di Instant Developer Cloud contiene una soluzione definitiva a queste problematiche: un meccanismo di configurazione basato su file di testo che permette di descrivere lo stato degli elementi dell’interfaccia utente in funzione di ruoli utente, username o lingua della sessione.
Tale meccanismo, denominato ACS, acronimo di Access Control Sheet, funziona in modo analogo alla configurazione grafica tramite CSS. I principi di funzionamento sono quindi i seguenti:
- È possibile definire a runtime lo stato degli elementi dell’interfaccia utente semplicemente modificando un file di testo: non è necessario installare una nuova versione dell’applicazione.
- Non richiede alcuna modifica al codice dell’applicazione.
- Le specifiche ACS hanno la precedenza rispetto a quanto scritto nel codice: un pulsante nascosto a causa dei ruoli utente non può essere più reso visibile dal codice dell’applicazione nemmeno impostando a true la proprietà visible.
- La definizione delle specifiche è molto semplice, simile alla sintassi CSS, e può agire anche a livello di intera applicazione oltre che di singola videata.
Per vedere in azione il framework ACS è disponibile il progetto di esempio ACS Design Patterns.
Configurazione del framework ACS #
Per attivare il framework ACS è necessario inserire nell’evento app.onStart, o comunque prima di far apparire videate da configurare, alcune righe di codice che ne forniscono la configurazione.
Le proprietà ed i metodi del framework ACS sono disponibili tramite l’oggetto app.acs. In particolare, durante l’inizializzazione della sessione è necessario fornire lo username dell’utente e l’elenco dei ruoli applicativi ad esso applicati. Un esempio di codice è il seguente:
App.Session.prototype.onStart = function (request)
{
…
app.acs.username = "rossim";
app.acs.addRole("mailing-admin");
app.acs.addRole("account-manager");
…
}
È importante notare che è possibile configurare per la sessione un solo username; è invece possibile aggiungere diversi ruoli utente. In questo modo è possibile gestire moduli applicativi diversi e avere ruoli specifici in ognuno di essi. L’array dei ruoli applicati alla sessione è disponibile, in sola lettura, tramite app.acs.userRoles. Oltre ad aggiungere ruoli è possibile rimuoverli tramite il metodo app.acs.removeRole.
Configurazione dell’insieme delle regole da applicare #
Vediamo ora come è possibile fornire al sistema le regole da applicare. Le regole possono essere fornite come risorsa caricata nell’IDE, come se fossero un file CSS, oppure direttamente a runtime.
Per definire le regole direttamente nell’IDE è possibile aggiungere all’applicazione una risorsa di tipo ACS e poi definire le regole direttamente nell’editor. Nell’immagine seguente viene mostrata la risorsa ACS del progetto di esempio ACS Design Patterns.
Quando viene aperta una sessione, verranno caricate tutte le risorse ACS inserite all’interno dell’applicazione o delle sue videate.
Se invece si preferisce caricare le regole ACS a runtime, è possibile utilizzare i metodi app.acs.load per caricare un file di testo che contiene le regole, oppure app.acs.loadByString per caricare una stringa di testo che contiene le regole.
È importante considerare che le regole non vengono memorizzate a livello di sessione ma di applicazione. Quindi se si carica più volte la stessa risorsa tramite load, oppure si applica più volte la stringa identificata dal medesimo id tramite loadByString, non si ottiene alcun effetto, a meno di non impostare a true il parametro reload dei suddetti metodi, che si occupa di ri-elaborare per l’intera applicazione l’insieme di regole passato.
Dopo aver modificato per una sessione l’insieme delle regole, è necessario chiamare il metodo app.acs.getRules, che seleziona le regole da applicare alla sessione corrente in base ai parametri della stessa ed infine se ci sono videate già aperte da riconfigurare, è necessario chiamare il metodo di view.refreshRoles sull’istanza della videata.
Sintassi del file ACS #
Un file ACS è simile come struttura ad un file CSS. È possibile inserire dei blocchi di regole, inframmezzati da commenti identificati da /* */. Un blocco di regole è costituito da un insieme di selettori e dall’insieme di regole da applicare.
Vediamo subito un primo esempio:
/* La mia prima regola ACS */
.admin {
MenuPage.*.adminGroup.visible: true;
}
Nell’esempio i commenti sono evidenziati in verde, i selettori in nero e le regole in colore blu. È importante che la parentesi graffa aperta appaia sulla medesima riga in cui sono definiti i selettori.
Selettori #
I selettori disponibili sono i seguenti:
- #<username> per selezionare uno specifico utente. Ad esempio #rossim identifica regole applicate se app.acs.username è uguale a rossim.
- .<userrole> per selezionare uno specifico ruolo utente. Ad esempio .admin identifica regole applicate se app.acs.userRoles contiene admin.
- @<lang> per selezionare una specifica lingua. Ad esempio @en, identifica regole applicate se app.langCode è uguale a en.
- * (asterisco) identifica regole applicate in ogni condizione.
I selettori possono essere composti in and scrivendoli uno di seguito all’altro separati da spazio, oppure in or separandoli da virgola. Ad esempio:
- .admin @en identifica regole applicata se per la sessione è attivo il ruolo admin e se la lingua è en.
- .admin, .manager identifica regole applicate se per la sessione è attivo il ruolo admin oppure il ruolo manager.
Si ricorda che i selettori devono essere scritti sulla stessa riga e che la parentesi graffa di apertura blocco deve essere sulla medesima riga.
Regole #
Una regola ACS specifica il valore di una proprietà di un elemento visuale di una videata. La sintassi è la seguente:
<targetView>.<element chain>. … .<property>:<value><!important|!default>;
targetView identifica il nome della videata a cui deve essere applicata la regola. Inserendo *, la regola verrà applicata a tutte le videate.
<element chain> identifica la catena dei nomi degli elementi visuali che identificano uno specifico elemento. È possibile utilizzare * per referenziare un qualunque elemento visuale a qualunque livello.
<property> è il nome della proprietà dell’elemento da impostare.
<value> è il valore a cui verrà impostata la proprietà. Deve essere nel formato nativo della proprietà, cioè se la proprietà è di tipo numerico, deve essere specificato un numero e così via.
<!important> è un modificatore opzionale che indica che la regola ha una priorità maggiore delle altre che non hanno il modificatore.
<!default> è un modificatore opzionale che indica che la regola imposta solo il valore di default della proprietà e consente al codice dell’applicazione di modificarla successivamente. Di solito, invece, una proprietà impostata tramite ACS risulta bloccata al valore assegnato.
Vediamo alcuni esempi:
Nasconde l’elemento di nome firstItem posizionato nella catena indicata della videata Pages:
Pages.split.menu.menucontent.menuList.firstItem.visible:false;
Nasconde tutti gli elementi che si chiamano firstItem nella videata Pages:
Pages.*.firstItem.visible:false;
Nasconde tutti gli elementi che si chiamano btnSave in tutte le videate:
*.*btnSave.visible:false;
Nasconde tutti gli elementi il cui nome contiene btn in tutte le videate:
*.*btn.visible:false;
Imposta la proprietà innerText di ogni elemento chiamato banner, ma il codice dell’applicazione può cambiarla:
*.*.banner.innerText:”Questo è un banner” !default;
Priorità delle regole #
La priorità delle regole applicate dipende dal tipo di selettori e dal flag !important specificato nella regola stessa.
L’ordine di priorità dei selettori è il seguente:
- # (utente)
- . (ruolo)
- @ (lingua)
- * (sempre valido)
Le regole vengono applicate dalla priorità più bassa verso la più alta, in modo che le maggiori prevalgono sulle altre.
Per maggiori dettagli sull’uso di ACS, si consiglia di studiare il progetto di esempio ACS Design Patterns.