Appian, struttura rule e funzioni essenziali.

In Precedenza abbiamo visto le caratteristiche del linguaggio Appian che lo caratterizzano, in questo articolo ne approfondiamo il tema della struttura delle rule e illustriamo le funzioni essenziali che ne permettono l’operatività.

Nel primo paragrafo rivedremo brevemente gli aspetti essenziali del linguaggio utilizzato, mentre nel secondo vedremo la struttura di una rule appian e le funzioni essenziali che vengono utilizzate tutti i giorni da ogni appian developer.

Caratteristiche principali del linguaggio SAIL

Abbiamo già illustrato che Appian ha un suo linguaggio proprietario, SAIL, che di fatto punta ad essere un linguaggio fortemente espressivo e flessibile: questi pregi vengono bilanciati da un neo come la totale mancanza di un sistema di cattura e gestione delle eccezioni all’interno di Sail.

Considerato quanto evidenziato possiamo dire che un buon codice appian deve avere queste caratteristiche:

  • Essere breve, conciso, pena un design rivedibile.
  • Deve prevedere una batteria di test per i casi più comuni.

I mattoncini principali per costruire gli script Sail sono :

  1. Variabili
  2. Costanti
  3. Le funzioni

Ogni istruzione è separata dalla virgola ed ogni oggetto si denota con un’espressione del tipo prefisso!nome_oggetto

Il prefisso dell’oggetto non è altro che il prefisso di dominio ed è utilizzato per disambiguare la tipologia di oggetto richiamata: un oggetto può essere una variabile, una rule appian, una funzione esterna o nativa.

Struttura e funzioni principali da utilizzare nelle rule

Elementi principali della struttura di una rule

Illustriamo i primi costrutti utili della struttura di una rule.

Abbiamo già visto i commenti, in Appian che si scrivono un po’ come Java e C#, si scrivono utilizzando la notazione slash e asterisco.

/* questo è un commento monolinea */

/* questo è un commento
Multilinea, ma carino */

Essendo un linguaggio funzionale, di fatto ogni nostro mattoncino nel software sarà funzione, in questo senso in appian non vediamo la differenza tra interfacce, rule e componenti per la comunicazione verso sistemi terzi (chiamati anche integrazioni): sono tutte rule e pertanto per richiamarle da un qualsiasi contesto dobbiamo utilizzare il dominio “rule”.

Le rule hanno degli input, chiamati Rule Inputs hanno prefisso di dominio “ri” e sono impostabili nel riquadro a destra da un qualunque editor di codice.

Funzioni essenziali

Ora che conosciamo la struttura delle rule, Illustriamo le funzioni essenziali che permettono l’operatività in appian: le funzioni possono originarsi dal sistema (funzioni di sistema) da eventuali plugin, da altre rule create.

Le funzioni di sistema hanno due domini, un dominio “a” e un dominio “fn”. La caratteristica principale del dominio “fn” è che possiamo ometterne la dichiarazione in fase di utilizzo, vale a dire scrivere if è la stessa cosa che scrivere fn!if.

Abbiamo già visto la funzione if come funzionei di sistema che permette di effettuare un calcolo in base a un valore booleano in ingresso. if(condizione, espressione se condizione vera, espressione se condizione falsa).
Ciò che ci restituisce l’if è dettato dalla prima espressione se la condizione è vera, dalla seconda se questa è falsa.

In questo contesto vediamo una nuova rule, fondamentale, ed è la localvariables.

Il dominio di questa funzione è “a” (pertanto per usarla dobbiamo indicarla come “a!localvariables“, tale funzione viene utilizzata per creare blocchi in cui dichiarare al proprio interno delle variabili con visibilità limitata al blocco, per dichiararle si utilizza il dominio “local”.

Facciamo un breve passo indietro, ed osserviamo un esempio di variabile come local!miaVariabile: il fatto che prima di ! ci sia “local” ci fa’ capire subito che ci si riferisce ad una variabile, nel caso in cui volessimo invocare la rule “miaRule” basterà scrivere rule!miaRule().

Un’altra funzione fondamentale è la funzione foreach, ha come dominio “a” ed è utilizzata per effettuare delle iterazioni su un array di dati. La funzione restituisce un array il cui il contenuto alla riga i-esima è quanto elaborato alla i-esima iterazione, eccovi un esempio:

All’interno del ciclo, possiamo utilizzare il dominio “fv” per avere accesso a due elementi importanti:

  • fv!index : che avrà come valore l’indice dell’iterazione corrente
  • fv!item : che avrà come valore l’elemento contenuto all’interno dell’iterazione i-esima.

Per chi viene dal mondo della programmazione (come il sottoscritto) troverà dolorosa l’assenza del ciclo for in Sail, tuttavia ci viene in soccorso la funzione fn!enumerate che non fa’ altro che creare un array da 0 a n-1 unico parametro di input.

Pertanto l’esempio riportato sopra si può riscrivere in questo modo:

qui abbiamo un’altra chicca del linguaggio: la somma viene effettuata su tutti gli elementi dell’array creando implicitamente un foreach.

Pertanto per ottenere lo stesso risultato basterà scrivere fn!enumerate(4)+2.

Appian, Processi e Modelli di processo

In questo articolo vogliamo approfondire le caratteristiche principali processi e dei modelli di processo di appian. Daremo i primi cenni delle componenti fondamentali.

I processi permettono di definire nel nostro BPM dei flussi di lavoro complessi per eseguire determinate attività considerando una serie di task.

Per capire bene alcune caratteristiche di Appian è importante approfondire le caratteristiche principali processi e dei modelli di processo di appian, pertanto effettuiamo una prima importante distinzione tra Modello di processo e Processo.

Modelli di Processo e Processo

I processi non sono oggetti autonomi, ma si basano su uno “scheletro” ovvero i modello di processo: tale oggetto permette di definire le operazioni che verranno eseguite dal processo. Per chi conosce Java/.Net possiamo dire tranquillamente che un processo è un’istanza del modello di processo.

Per chi non conosce Java/.NET possiamo paragonare la relazione a un percorso stradale:

  • Il modello di processo è un po’ come una mappa ci permette di andare dal punto A al punto B, definendo una serie di “alternative” da prendere in considerazione nel caso un semaforo rosso, traffico oppure un incidente.
  • il processo è un viaggio che eseguiamo in un deteminato momento partendo da A e al punto B, tutte le scelte possibili e le scelte che abbiamo faranno comunque parte del processo anche se non le dovessimo seguire.

Un processo sufficientemente lungo comporta scelte, fermate come “i rifornimenti”, seguire determinati percorsi autostradali e a volte passare per dei telepass. Possiamo paragonare le scelte a dei gateway e ogni attività ad un task, questi possono essere classificati, in prima battuta, in due tipologie:

  • eseguiti dal sistema
  • eseguiti dall’utente

I task eseguiti dal sistema si definiscono “unattended” mentre i task eseguiti da un utente sono “attended”.
Un task “attended”, seguendo il nostro esempio, può essere il rifornimento del carburante. Un task “unattended” può essere tranquillamente il pagamento del casello autostradale mediante il telepass.

Un modello di processo deve avere : un nome, un inizio e una fine collegati tra di loro mediante un arco. Notiamo che i punti di inizio e fine sono a tutti gli effetti dei task unattended.

Lo user input task

Un buon esempio di un’attività attended è l’user input task.

Tale task mostra un’interfaccia su cui l’utente opera e si riterrà concluso quando ne viene effettuato “il submit”. Per effettuare un submit occorre cliccare su un tasto avente proprietà di submit (o invio), avendo cura di aver superato la validazione delle form.

Ad esempio, si impostia la proprietà submit su un bottone in questo modo:

a!buttonLayout(
          secondaryButtons: a!forEach(
            items: local!styles,
            expression: a!buttonWidget(
              size: local!size,
              style: fv!item,
              submit: true()
            )
          )
)

Impostiamo la proprietà submit impostato a true il valore della proprietà submit del pulsante.

Lo start form

Dal nodo iniziale parte il nostro processo, che può avviarsi con o senza interfaccia, o più propriamente una start form.
Attenzione, quando chiamiamo il nostro processo ed esso è con una start form, il processo non è ancora partito! il processo partirà soltanto una volta effettuato il submit!

Il submit indica ad Appian di prelevare i risultati della nostra form e passarle, a sua volta, al processo e permettergli di avviarsi.

Le interfacce in Appian si scrivono in SAIL e quindi sono soggette alle stesse proprietà di cui abbiamo discusso in precedenza.

Approfondiremo in seguito il meccanismo di passaggio delle variabili ai processi.

Appian, il linguaggio che rende “lowcode” il BPM.

In questo articolo approfondiamo le caratteristiche principali della componente “Codice” del BPM, delineando le caratteristiche che lo rendono lowcode.

Nel precedente Articolo abbiamo visto le componenti principali di Appian che lo caratterizzano, qui approfondiamo le caratteristiche principali della componente “Codice” del BPM, ovvero SAIL delineando le caratteristiche che lo rendono lowcode.

SAIL è lowcode

Abbiamo già illustrato che Appian ha un suo linguaggio proprietario, SAIL, che di fatto è un linguaggio che punta ad essere un linguaggio fortemente espressivo, in particolare:

  • È un linguaggio Funzionale, al cui interno abbiamo la possibilità di usufruire di funzioni di second’ordine.
  • È debolmente tipato, anche se tipizzando le variabili in ingresso (rule input) l’ambiente adotta i controlli tipici della tipizzazione forte, eccetto per la possibilità di eseguire conversioni di tipo implicite;
  • È fortemente espressivo, inoltre è utilizzato sia per la costruzione di rule che di interfacce
  • Non ha un sistema di cattura e gestione delle eccezioni, questo fa’ sì che bisogna usare con oculatezza le rule e le interfacce in un applicativo robusto.

Le caratteristiche appena citate permettono di scrivere codice molto espressivo in grado di effettuare operazioni anche molto complesse con poche righe di codice.
Ad esempio, consideriamo un dizionario che schematizziamo in Json con un costrutto del tipo :

[{“nome”:”Vincenzo”,”cognome”:”Reggina”,”professione”:”Sviluppatore”},{“nome”:”Tizio”,”cognome”:”Rossi”,”professione”:”Pittore”},{“nome”:”Caio”,”cognome”:”Verdi”,”professione”:”Designer”}]
e da questo json vogliamo estrarre solo gli elementi del dizionario “Nome”.

Dobbiamo semplicemente scrivere il seguente codice:
a!fromJson(local!elenco).nome

SAIL come linguaggio di programmazione

Appian punta molto sul fatto che il suo BPM sia “lowcode”. Il motivo è chiaro: “lowcode” si traduce in tempi di sviluppo più rapidi, ma per farlo il proprio linguaggio, ha determinate caratteristiche. Il fatto SAIL sia un linguaggio funzionale con una forte espressività ci permette di scrivere segmenti di codice fortemente concisi che ne assicurano anche una forte riusabilità all’interno della nostra applicazione. Gli script in virtù del fatto che il linguaggio è debolmente tipato permettono sono facilmente generalizzabili. Tutto oro? assolutamente no.

Il fatto che gli script siano debolmente tipati e che non ci sia una gestione degli errori fa’ sì che gli script devono quanto più possibile essere brevi e concisi pena la decadenza della manutenibilità degli script stessi.

Primi costrutti: commenti e if.

A questo punto illustriamo i primi costrutti utili per un semplice script.

Il primo costrutto che vedremo sono i commenti, in Appian un po’ come Java e C#, si scrivono utilizzando la notazione slash e asterisco.

/* questo è un commento monolinea */

/* questo è un commento
Multilinea, ma carino */

Tutto qui,  per quanto riguarda la componente SAIL, mentre per la componente BPM è consigliato (leggi anche obbligatorio) l’utilizzo delle annotation dei processi per inserire dei commenti, ma vedremo poi.

Il secondo costrutto che vediamo è l’if il quale è una (ovviamente) funzione che differenzia il risultato in base a un valore booleano in ingresso.

if(condizione, espressione se condizione vera, espressione se condizione falsa).

Ciò che ci restituisce l’if è dettato dalla prima espressione se la condizione è vera, dalla seconda se questa è falsa.

ad esempio if(0=1,“Appian”,”Computer”) restituirà sempre la stringa “Computer”.

Appian, un BPM votato alla Robotic Process Automation

Vediamo le caratteristiche essenziali di Appian, un BPM che sta’ guadagnando sempre più fette di mercato.

Appian è un software per la modellazione di processi di business. Tra le funzionalità più pubblicizzate abbiamo tempi di sviluppi molto rapidi. Appian come piattaforma si divide in più ambienti:

  • Tempo
  • Designer
  • Console di amministrazione
  • Sito

Tutte queste funzionalità sono fruibili tramite browser, quindi lo sviluppatore non dovrà installare nulla sulla propria macchina.

In questo primissimo articolo partiremo dalla componente base di appian: il custom data type, che altro non è che il modo di rappresentare di appian delle entità e poi daremo un brevissimo escursus sul linguaggio di scripting utilizzato.

I Custom Data Type (CDT)

I Custom Data Type rappresentano le entità di base di Appian, descrivono oggetti d’interesse della piattaforma.
Nei custom datatype dobbiamo mettere tutto ciò che ci occorre per descrivere l’entità, ad esempio, se vogliamo descrivere l’oggetto persona semplicemente possiamo usare: il nome e il cognome come testo, mentre la data di nascita come data.

I CDT possono essere innestati fra loro ovvero possono contenere un altro CDT al suo interno, ad esempio possiamo considerare la città di nascita un oggetto di tipo Citta, che potrebbe contenere al suo interno Nome, Regione e Provincia di appartenenza e altro.  I CDT possono avere anche particolari campi che possono essere un elenco di informazioni come ad esempio un elenco di numeri di telefono.

Appian è lowcode: il linguaggio

Appian ha un suo linguaggio proprietario, il cui nome non viene mai specificato e che informalmente chiameremo SAIL. Tra le caratteristiche principali abbiamo:

  • È un linguaggio Funzionale
  • È debolmente tipato
  • È utilizzato sia per la costruzione di rule che di interfacce
  • Non ha un sistema di cattura e gestione delle eccezioni

SAIL, nella visione di Appian, è un linguaggio semplice da imparare che permette allo sviluppatore di avere la sufficiente espressività per automatizzare i processi di business.

SAIL possiede, in sintesi, le seguenti tipologie di funzioni:

  • Costrutti dichiarativi
  • Le variabili e i dizionari
  • I costrutti condizionali e di selezione
  • I costrutti ciclici
  • Funzioni di sistema e funzioni di custom

Nel proseguo dei nostri articoli vedremo nel dettaglio tutte queste tipologie di funzioni.

Appian è attenta al mondo RPA

Appian è particolarmente attenta alla Robotic Process Automation. In particolar modo, il BPM è integrato con l’applicativo RPA di Blueprism. Tuttavia in futuro verrà estesa l’integrazione anche verso l’altro competitor di mercato: UiPath.

L’integrazione in particolar modo rende semplice l’invocazione dei robot all’interno di un modello di processo: la chiamata viene effettuata con uno Smartservice che conserverà al suo interno tutti i dati per effettuare la chiamata, al pari di una integrazione verso un server mail o altri servizi della piattaforma. Vedremo in futuro tale integrazione.

Programmazione SAS, i dataset

In questo articolo approfondiamo la struttura base per l’elaborazione dei dati in SAS.

Principi base

Il dataset, molto semplicemente, è una struttura che contiene i dati da elaborare o mostrare.
I dataset all’interno di SAS vengono custoditi in librerie che raccolgono un insieme di dataset.
la libreria base di SAS si chiama work bisogna considerare tale libreria come se lavorasse esclusivamente in memoria: se avviene un riavvio (o anche un fail) del sistema il contenuto di tale libreria verrà azzerato. L’esplicitazione della libreria work è facoltativa.

La dichiarazione più semplice di un dataset è la seguente:

Data targetdataset;
set work.mydataset;
run;

Il dataset “targetdataset” ora punterà al valore presente nel dataset “mydataset”, ricordiamo che entrambi i dataset sono contenuti nella libreria work.

Caricamento dataset da codice

Il dataset può essere caricato mediante file, mediante database o in altri modi, in questo contesto ci limitiamo a mostrare il caricamento del dataset da codice utilizzando l’istruzione datalines:

data weight2;
input IDnumber $ Week1 Week16;
WeightLoss2=Week1-Week16;
datalines;
2477 195 163
2431 220 198
2456 173 155
2412 135 116
;

L’istruzione input ci permette di definire quali colonne verranno lette tramite l’istruzione datalines.
Utilizzare colonne in questo caso è improprio: essendo SAS nato per la statistica pertanto è più pertinente chiamare le colonne variabili.

Il simbolo del $ permette di dire che la variabile “IDnumber” è una  stringa, mentre Week1 e Week16 sono da considerarsi numeri.

Tra l’istruzione datalines e il ; sono presenti l’insieme delle tuple presenti nel dataset.

Anche utilizzare tuple in questo caso è improprio: è pertinente chiamare le tuple osservazioni.

Logica dei dataset

Bisogna considerare che SAS all’atto della definizione di un dataset ne effettua una vera e propria costruzione ciclica. In particolare utilizzando l’istruzione data si crea un ciclo per cui ad ogni elemento in input viene creato un elemento nel dataset. Ad esempio:

Data targetdataset;
retain acc;
  set work.mydataset;
  acc = acc + col;
run;

Nel dataset targetdataset per ogni occorrenza presente in mydataset verrà aggiunta alla variabile acc il valore di col. L’istruzione retain permette di conservare il valore di acc attraverso le iterazioni.

Selezionare le variabili

Si possono proiettare dal dataset le variabili utilizzando l’istruzione drop. Per il mondo database è come indicare le colonne nella clausola “select”.

Esempio:

Data targetdataset;
  set work.mydataset;
  drop mywork;
run;

Nel dataset targetdataset verrà eliminata la variabile mywork che tuttavia resterà nel dataset mydataset.

L’istruzione keep è il duale dell’opzione drop poiché con la stessa sintassi permette di mantenere nell’esempio precedente solo la variabile mywork.

Selezionare le osservazioni

Per selezionare le osservazioni dal dataset si utilizza, mutuandola dal mondo database l’opzione where.  Anche la sintassi in questo caso è mutuata dal mondo database.

Data targetdataset;
  set work.mydataset;
  where country=’av’ and job_title contains ‘rep’;
run;

Nel caso in cui un valore della variabile è missing, l’intera esprezione risulterà missing. Questo ha un forte impatto sopratutto nelle espressioni aritmetiche poiché laddove si potrebbe pensare a una valorizzazione con 0, l’esistenza di missing dev’essere considerata attentamente.

Missing è simile al “null” del paradigma Object Oriented, ma molto diverso da 0. Ad esempio:

Data targetdataset;
set work.mydataset;
c=a+b;
where c > 0;
run;

Se a =4  e b è missing il valore di c sarà missing. La where non prenderà mai in considerazione l’osservazione e non la mostrerà a video.

Log Injection

In questo articolo trattiamo la Log Injection, una tipologia di attacco di per sé non grave, ma che può nascondere attacchi ben più importanti.

Il Log Injection (chiamato anche Log Forging) è una attacco in cui l’attaccante riesce a scrivere direttamente all’interno delle informazioni di tracciamento di un applicativo.

All’interno delle applicazioni c’è necessità di tenere traccia degli eventi per risolvere tempestivamente e in maniera efficace i possibili problemi futuri. Le applicazioni in genere utilizzano dei file di testo per memorizzare le informazioni sugli eventi accaduti nel sistema e in caso di problemi, tali log vengono analizzati. L’analisi dei log può essere manuale o automatizzata. L’analisi automatizzata viene utilizzata per filtrare le informazioni “quotidiane” dalle informazioni relativi ad eventi non ordinari o a problemi.

Un attacco di log injection può essere indirizzato al sistema di analisi, tramite l’inserimento di comandi, oppure utilizzato per coprire le tracce lasciate da un attacco precedente, molto più grave.

Spesso chi si occupa della manutenzione tende a creare log che (si spera) permettano di essere un buon supporto nella fase di sviluppo delle correttive. A volte vengono inseriti anche gli input provenienti dai form in cui digita l’input è deciso dall’utente.

Consideriamo l’esempio che segue e che trovate sul sito dell’OWASP.

Esempio


String val = request.getParameter("val");
try {
     value = Integer.parseInt(val);
}
catch (NumberFormatException) {
     log.info("Failed to parse val = " + val);
}

Il frammento di codice non è poi così raro, tuttavia sottintende la volontà del programmatore di porre rimedio velocemente a eventuali errori di conversione dei valori.

Risoluzione

Inserire direttamente gli input utente nei log è una misura estrema, che bisogna utilizzare con parsimonia.
Pertanto una prima soluzione al problema rimuovere l’istruzione che scrive il contenuto dell’input dell’utente direttamente nel log. Pertanto nel caso specifico il frammento che segue risulterebbe ugualmente valido:


String val = request.getParameter("val");
try {
     value = Integer.parseInt(val);
}
catch (NumberFormatException) {
     log.info("Failed to parse val");
}

Se è strettamente necessario salvare gli input utenti nei log, tali input vanno verificati. Spesso è più importante scrivere la causa dell’errore, e riferendoci all’esempio potremmo indicare il carattere non consentito che è stato utilizzato.

Fonti:

Owasp, log injection

Programmazione SAS, fondamenti

Con questo articolo inauguro una nuova serie di articoli riguardanti le fondamenta di SAS.

Cos’è SAS?

SAS è l’acronimo di “Statistical Analysis System” ed è una suite di prodotti software che permettono di effettuare tutte quelle operazioni ricorrenti negli ambiti in cui si effettua l’analisi di grandi moli di dati.

Le componenti basilari di SAS sono le seguenti:

Base SAS Software, utilizzato per l’import e la gestione dei dati
SAS procedures software, per l’analisi, operazioni comuni sui dati ed il reporting
Macro facility, per estendere e personalizzare le applicazioni
DATA step debugger, il debugger utilizzato da SAS.
Output Delivery System (ODS), permette di effettuare l’output dei risultati ottenuti in SAS.
SAS windowing environment/SAS Studio, sono softtware che permettono di eseguire e testare le applicazioni sviluppate in SAS.

I contenuti di questa serie di Articoli

L’idea che c’è dietro questi articoli è di far comprendere al lettore cos’è un Software SAS, mostrarne le potenzialità, le fondamenta e qualche limite.
Non mi soffermerò dietro alle finezze o ai casi particolari in quanto tali, tenterò di concentrarmi maggiormente sulle istruzioni basilari e sugli aspetti come i vantaggi e gli svantaggi nell’utilizzo della piattaforma.

Le fondamenta  di un software SAS

I software SAS sono essenzialmente composti da due tipologie di istruzioni.

  1. Data step
  2. Proc Procedure

Il Data Step si utilizza in per creare nuovi Dataset o per effettuare degli output su file in formato testo.
Le Proc procedure effettuano operazioni su Dataset o permettono di effettuare operazioni di analisi e reportistica su essi.

Questa suddivisione già ci permette di capire come utilizzare SAS: importare dati, manipolarli e utilizzarli. È tutto qui: il focus sono i dati, il software permette di gestire molte fonti di input ed è in grado di catturare gran parte delle eccezioni e/o sfumature degli stessi.

Un semplice Data Step è il seguente:

data myContacts;
set SASHELP.contacts;
run;

Questo dataset è una copia speculare del dataset SASHELP.contacts all’interno del dataset myContacts.

Il dataset “contacts” si trova in un grande raccoglitore di dataset, ovvero una libreria,  che si chiama SASHELP.
Il dataset “myContacts” si trova in memoria, tale libreria si chiama Work ed è implicita.

Al termine della sessione SAS, tutto il contenuto della libreria Work viene eliminato.

Per stampare un dataset occorre eseguire una procedura di print, come segue:

proc print data=MYcontacts;
run;

Attenzione, SAS non è case sensitive, pertanto “MYcontacts” e “myContacts” sono due nomi che puntano alla stessa libreria.

Ogni istruzione termina con un “;”,  normalmente sia i Data step che le Proc terminano con l’istruzione “run”.

Fonti:

Dataset definition

Proc Print definition

La Sicurezza del software e la Path Manipulation

Con questo articolo avviamo una serie di articoli che tratteranno di sicurezza informatica, dal punto di vista della manutenzione software, iniziamo dalla Path Manipulation

In questi articoli voglio inquadrare il problema dal punto di vista della manutenzione del software, almeno per i problemi più classici. Dal punto di vista dello sviluppo, molte delle problematiche non sono più riscontrate poiché nei nuovi framework poiché adottano vi sono soluzioni built-in.

Spesso l’esistenza di vulnerabilità note da anni si può assimilare a errori di cattiva programmazione piuttosto che effettivamente a vulnerabilità dovute ad obsolescenza del software o della piattaforma. Alla luce dei motivi espressi trovo inutile a riportare semplicemente un decalogo “soluzione/risoluzione” del problema.

Spesso la sicurezza del software viene attuata istanziando un flusso con dei software che effettuano l’analisi statica e/o dinamica del codice sorgente del software d’interesse. A valle di tale analisi il software creerà delle segnalazioni relative alle vulnerabilità trovate, da valutare. Chiaramente la valutazione è d’obbligo sostanzialmente per due motivi:

  1. il software che effettua l’analisi potrebbe contenere a sua volta dei bug che possono far sì che le segnalazioni non siano corrette.
  2. il software che effettua l’analisi riporta la vulnerabilità solo se il calcolo effettuato è nel range di confidenza prefissato/configurato: questo vuol dire che se è riportata una segnalazione vuol dire che probabilmente vi è la vulnerabilità, non la certezza.

Il flusso così implementato nasconde un’ulteriore insidia: non verranno mai risolte quelle vulnerabilità che seppur presenti non vengono considerate: se l’elaborazione non rileva il problema oppure esso non è nel range di confidenza prefissato, la segnalazione non verrà mai effettuata e la vulnerabilità resta lì dov’è.

Un’ulteriore insidia (non meno pericolosa) è la stabilità del workflow instanziato.

Il workflow instanziato che viene istanziato al fine di migliorare la sicurezza del software non deve subire brusche evoluzioni, come il cambiamento della configurazione del software che effettua l’analisi, pena la risoluzione di vulnerabilità che possono essere considerate di “secondo piano” rispetto a vulnerabilità che possono essere rilevate mediante “la nuova configurazione” e viceversa.

La Path Manipulation (chiamata anche Directory traversal attack oppure path traversal attack) è una vulnerabilità per cui è possibile, tramite input esterni, leggere o scrivere in settori in cui non è previsto, nel sistema, la lettura e la scrittura dei file.

Il problema è molto serio: scrivere in particolari settori del file system può portare ad accessi (anche di tipo amministrativo) all’applicazione oggetto dell’attacco.

Per l’OWASP, la Path Manipulation si verifica quando si ottengono i seguenti effetti:

  1. l’attaccante può specificare un percorso utilizzato in un’operazione sul filesystem.
  2. Specificando la risorsa, l’attaccante ottiene dei privilegi che altrimenti che non sarebbero consentiti.

L’esempio triviale in questo senso è quello di consentire il nome del file dall’esterno dell’applicazione senza alcuna tipologia di filtro: tramite parametri esterni, un utente può definire il percorso di un file da cancellare o da modificare sul server.

Consideriamo un esempio di applicazione ASP.NET, in linguaggio C#:

Vogliamo effettuare la stampa di un report ed effettuiamo la chiamata di un report passando via GET (tramite url) il nome del report, cancellando il vecchio report eventualmente presente, il codice potrebbe presentarsi così:

[csharp]
String rName = Request.QueryString["reportName"];
string path = "C:\\Temp\\" + rName;
if (File.Exists(path))
{
File.Delete(path);
}
[/csharp]

Questo è un buon esempio di Path Manipulation: modificando opportunamente il parametro “rName” posso navigare nel filesystem e cancellare a piacere qualunque file.

Ad esempio se rName viene impostato a “..\psw.txt” la funzione File.Exist si assicurerà che esiste il file ed eventualmente l’istruzione delete lo può cancellare.

Esistono più soluzioni, tutte dipendenti dal contesto.

  1. Creare una WhiteList di valori ammessi in input: tale soluzione è definitiva ma spesso inapplicabile, poiché spesso i valori sono “arbitrari” e spesso vengono passati valori di input “di comodo” in fase di sviluppo.
  2. Creare una Blacklist di caratteri ammessi in input: in questo modo si delimita il problema alla cartella di riferimento, ovvero alla cartella “Temp” dell’esempio, in questo modo si limitano i possibili danni.

Spesso la soluzione più ragionevole da adottare è la 2, purché vengono adottate alcune osservazioni:

  1. Si contestualizza il flusso del software all’utente attualmente autenticato all’applicazione
  2. La cartella sia utilizzata solo come interscambio per rispondere alle richieste provenienti dall’esterno.

Esempio:

[csharp]

String rName = Request.QueryString["reportName"];
String nomeUtente = Session["Username"];
string path = "C:\\Temp\\" + nomeUtente + "\\" + rName;
if (File.Exists(path))
{
File.Delete(path);
}
[/csharp]

La cartella dedicata all’utente impedisce la cancellazione di file destinati agli altri utenti (potremmo avere anche un problema di lettura), inoltre se la cartella non sarebbe d’interscambio potrebbe essere cancellato o modificato un file necessario al funzionamento dell’applicazione stessa.

Link esterni/fonti di approfondimento:
Owasp
Wikipedia

Serializzazione in C#

In questo articolo illustro le basi della serializzazione in C Sharp

La serializzazione è quel processo per cui si vuole che delle istanze di un determinato Tipo possano essere trasformate in oggetti manipolabili dalla CRL mediante strutture generiche come stringhe e file. Ad esempio, consideriamo come tipo serializzabile la classe “Persona” una sua istanza, ovvero un oggetto di questo tipo, può esser serializzata per i scopi più vari, ad esempio:

  1. La trasmissione di una istanza ad un server o ad un client sulla rete
  2. La persistenza di una istanza su file system
    e così via…

La serializzazione avviene considerando un set di componenti base da utilizzare i quali sono : un serializzatore e una fonte di input dei dati. La fonte dei dati può essere: uno stream o una stringa. Il  serializzatore ci permette di definire il tipo di serializzazione che vogliamo usare nel nostro software: i tipi più famosi di serializzazione sono Binario, XML e JSON.

In C# abbiamo due “modi di pensare” della serializzazione, in sostanza, si può decidere di definire un contratto chiamato DataContract oppure di serializzare integralmente l’oggetto, annotando solo le variazioni richieste.

DataContract

La classe DataContract permette di serializzare le classi mediante “un contratto” da definire all’interno delle stesse.
La definizione delle classi avviene mediante annotazioni, indipendentemente dalla visibilità degli elementi annotati, in particolare si usano:

  1. L’annotazione DataContract, che definisce il contratto di serializzazione dell’oggetto in elementi atomici
  2. L’annotazione DataMember che definisce un elemento della serializzazione.

A seguire un esempio di classe annotata mediante DataContract.

[csharp]
[DataContract(Name = "Persona", Namespace = "http://www.persona.com")]
class Persona
{
[DataMember(Order =1, Name ="NomeDiBattesimo")]
public string nome {
get { return _nome; }
set { _nome = value; }
}
private string _nome;

[DataMember(Order = 2,IsRequired = false,Name ="Cognome", EmitDefaultValue = true)]
public string cognome
{
get;
set;
}
[/csharp]

L’attributo DataContract può avere le seguenti opzioni:

  • Name : Attribuisce un nome la contratto utilizzato, risulta utile per evitare conflitti di nomi.
  • NameSpace : Attribuisce il namespace da utilizzare, risulta utile nei file XML.
  • IsReference : Permette di istruire il serializzatore affinché vengano gestiti i riferimenti circolari all’interno del file XML.

Considera, ad esempio, che l’oggetto persona abbia il riferimento ad un elenco di persone associate: in questo caso due persone (ad esempio dei figli) possono essere associati ad una sola persona (il padre) e pertanto verrà visualizzato un attributo nel file XML risultante che collega il figlio con il suo padre.

L’attributo DataMember può avere le seguenti opzioni:

  • Order : Permette di definire l’ordine di apparizione rispetto al padre del nodo, nel nostro esempio, nome apparirà prima di cognome.
  • Name : Attribuisce l’etichetta visualizzata dal membro.
  • EmitDefaultValue : permette di utilizzare comunque il membro anche se non valorizzato o presente, considerando il valore di default.
  • IsRequired : se il campo non è valorizzato in fase di deserializzazione (lettura dalla sorgente), viene lanciata un’eccezione. In fase di serializzazione l’utilizzo dev’essere in accordo con l’opzione EmitDefaultValue: ovvero se il campo è obbligatorio (IsRequired è true) allora dev’essere valorizzato nell’oggetto oppure EmitDefaultValue dev’essere true.

Con il DataContract possiamo ottenere la serializzazione in due differenti tipologie di formato: XML e JSON ottenibili rispettivamente con le classi DataContractSerializerDataContractJsonSerializer.

Entrambe le classi sono specializzazioni della classe astratta XmlObjectSerializer, per entrambe le classi bisogna passare l’oggetto Type relativo all’oggetto da serializzare.

[csharp]
DataContractSerializer dcs = new DataContractSerializer(typeof(Persona));
Persona p = new Persona();
p.cognome = "CognomePersona";
p.nome = "NomePersona";
p.Indirizzo = "via dei martiri 28";

FileStream fs = File.OpenWrite("serializzato.xml");
dcs.WriteObject(fs,p);
fs.Flush();
fs.Close();
[/csharp]

In questo caso abbiamo scelto di scrivere su file system l’oggetto persona, leggendo il file mediante File.OpenRead
e utilizzando il metodo ReadObject possiamo leggere il file. Osserva che è necessario un Cast per convertire l’oggetto in modo tale da assegnarlo ad un reference del tipo “Persona.”

[csharp]
FileStream fsr = File.OpenRead("serializzato.xml");
Persona per = (Persona)dcs.ReadObject(fsr);
[/csharp]

Nel caso dobbiamo creare dei file JSON invece dei file XML, dobbiamo semplicemente sostituire la classe  DataContractSerializer con la classe DataContractJsonSerializer.

NetDataContractSerializer

L’ultimo esempio di serializzatore con contratto che vedremo è il : NetDataContractSerializer.

Questo serializzatore è molto simile al DataContractSerializer, infatti serializza gli XML con una limitazione: vi p bisogno che il tipo serializzato è presente nella CLR che deserializza il file.

La serializzazione è simile, eccetto per il fatto che non vi è bisogno di passare il tipo da serializzare nel costruttore, tale tipo infatti dev’essere presente nel software. Il serializzatore è in grado di serializzare gli oggetti con
DataContractAttribute  oppure con i SerializableAttribute e serializza anche i tipi che implementano ISerializable (infatti è possibile usare Serialize e Deserialize).

[csharp]
NetDataContractSerializer dcs = new NetDataContractSerializer();
….
FileStream fs = File.OpenWrite("serializzato.xml");
dcs.WriteObject(fs,p);

[/csharp]

Ometto per brevità la deserializzazione che è la medesima.

Serializzazione senza contratto

Precedente alla serializzazione con DataContract esiste anche la serializzazione basata sulla struttura degli oggetti. Tale tipo di serializzazione è decisamente più macchinosa, ma permette di avere maggior controllo sulla serializzazione.

Normalmente si possono serializzare gli oggetti in due formati, in Binario e in XML rispettivamente con il BinaryFormatter e il XmlSerializer.

BinaryFormatter

Tale tecnica consiste nel salvare le classi utilizzando il formato binario, in questo caso il serializzatore salverà integralmente l’oggetto affinché possa essere recuperato, contrariamente ai metodi con DataContract il metodo da utilizzare sono Serialize Deserialize.

Innanzitutto aggiorniamo la classe Persona affinché sia pulita per la serializzazione senza contratto.

[csharp]
[Serializable]
public class Persona
{
public string nome {
get { return _nome; }
set { _nome = value; }
}
private string _nome;

public string cognome {
get { return _cognome; }
set { _cognome = value; }
}
private string _cognome;

public string Indirizzo
{
get {return indirizzo;}
set {indirizzo = value;}
}
private string fattiSpece;

public void setMiaFattispece(String str) {
fattiSpece = str;
}
private string indirizzo;

}

static void SerializzazioneBinaria(){
Persona p = new Persona();
p.cognome = "Ronaldo";
p.nome = "Cristiano";
p.Indirizzo = "Napoli, via dei martiri 1";
p.setMiaFattispece("assente");

//creo lo stream di scrittura
FileStream fs = File.OpenWrite("serializzato.bin");
BinaryFormatter b = new BinaryFormatter();
b.Serialize(fs,p);
fs.Flush();
fs.Close();
}

FileStream fsr = File.OpenRead("serializzato.bin");
Persona np = (Persona)b.Deserialize(fsr);
fsr.Close();
[/csharp]

In questo caso otterremo che l’oggetto verrà serializzato nel suo complesso, infine notiamo che l’uso dell’attributo Serializable è obbligatorio per effettuare la serializzazione binaria.

Infine se non vogliamo serializzare alcuni attributi pubblici è possibile utilizzare l’attributo NonSerialized.

XmlSerializer

Operativamente il serializzatore XmlSerializer è molto simile al BinaryFormatter.

[csharp]
XmlSerializer dcs = new XmlSerializer(typeof(Persona));
Persona p = new Persona();
p.cognome = "Milik";
p.nome = "Arek";
p.Indirizzo = "via dei martiri 13";
p.setMiaFattispece("assente");
Console.WriteLine(p.ToString());

FileStream fs = File.OpenWrite("serializzato.xml");
dcs.Serialize(fs,p);
fs.Flush();
fs.Close();

FileStream fsr = File.OpenRead("serializzato.xml");
Persona per = (Persona)dcs.Deserialize(fsr);

Console.WriteLine("Deserializzato da file");
Console.WriteLine(per.ToString());
Console.ReadKey();

fsr.Close();
[/csharp]

Facendo girare l’esempio osserveremo una grande prima differenza, il field fattiSpecie non verrà serializzato, perché sono serializzati soltanto gli attributi pubblici.

Inoltre XmlSerializer permette di definire degli attributi per regolare in maniera fine la serializzazione:

  1. XmlRootAttribute, permette di definire gli attributi del nodo root dell’oggetto, in particolare si possono utilizzare le seguenti opzioni:
    1. ElementName, permette di definire il nome del nodo da visualizzare come root.
    2. Namespace, permette di definire il namespace da utilizzare.
    3. IsNullable, permette di impostare, tramite true o false, se l’elemento può essere nullo.
    4. DataType, preleva o setta, tramite uno schema XSD da utilizzare nella serializzazione.
  2. XmlElement, permette di impostare il campo come un elemento dell’albero XML.
    1. ElementName, permette di definire il nome del nodo da visualizzare nel file XML.
    2. DataType, permette di impostare, tramite una stringa, il tipo dell’elemento.
    3. Form, permette di attribuire un valore che permette di esprimere se un elemento è qualificato.
    4. IsNullable, permette di impostare, tramite true o false, se l’elemento può essere nullo,
    5. Namespace, permette di definire il namespace da utilizzare.
    6. Order, permette di definire l’ordine di apparizione del campo all’interno del file serializzato.
    7. Type, permette di impostare, tramite una stringa, il tipo dell’elemento.
  3. XmlAttribute, permette di impostare il campo come un attributo del nodo radice. Se si vuole impostare l’attributo ad un nodo intermedio, il nodo itermedio dev’essere convertito a classe ed effettuare materialmente una inclusione.
    1. ElementName, permette di definire il nome dell’attributo del nodo.
    2. DataType, preleva o setta, tramite uno schema XSD da utilizzare nella serializzazione.,
    3. Form, permette di attribuire un valore che permette di esprimere se un elemento è qualificato.
    4. Namespace, permette di definire il namespace da utilizzare.
    5. Type, permette di impostare, tramite una stringa, il tipo dell’elemento.
  4. XmlIgnore, permette di ignorare il campo dell’oggetto anche se questo è public.
Differenze tra DataContractSerializer e XmlSerializer

A questo punto è lecito porsi una domanda: quando conviene utilizzare un contratto?

La risposta (come sempre nel caso dell’informatica) è dipende.

Sostanzialmente nel caso degli XmlSerializer c’è la possibilità di dichiarare gli attributi utilizzando il tag “XmlAttribute” cosa che non è possibile effettuare nel DataContract, tuttavia il DataContract permette di passare da JSON a XML con molta semplicità.

Un caso particolare, il JavaScriptSerializer

Il JavaScriptSerializer è un serializzatore senza contratto che permette di effettuare le operazioni di serializzazione di JSON su stringa. L’utilizzo di tale serializzatore è scoraggiato da Microsoft ed è utilizzato solo per fini interni al framework .Net.

Vediamone l’utilizzo:

[csharp]
JavaScriptSerializer dcs = new JavaScriptSerializer();
StringBuilder fs = new StringBuilder();

Persona p = new Persona();
p.cognome = "Pavoletti";
p.nome = "Leonardo";
p.Indirizzo = "via dei martiri 13";
p.setMiaFattispece("assente");
Console.WriteLine(p.ToString());

dcs.Serialize(p, fs);
string fsr = fs.ToString();
Persona per = (Persona)dcs.Deserialize(fsr, typeof(Persona));
[/csharp]

La serializzazione avviene seguendo le annotazioni utilizzate per la classe XMLSerializer.

Pertanto quest’ultimo modo di serializzare che vediamo, rende la serializzazione senza contratto ugualmente potente rispetto alla serializzazione con contratto.

Facilitazioni alla serializzazione senza contratto

Opzionalmente è possibile implementare l’interfaccia ISerializable e utilizzare i SerializableAttribute.

ISerializzable

L’interfaccia ISerializzable consente ad una classe di disporre del metodo GetObjectData che permette di eseguire delle operazioni prima della serializzazione al fine di arricchire ulteriormente le informazioni della serializzazione.

SerializableAttribute

L’attributo SerializableAttribute permette di indicare che una classe è Serializzabile, si può abbreviare utilizzando semplicemente Serializzable (che abbiamo visto per la serializzazione binaria).

Inoltre è possibile utilizzare degli attributi che permettono di definire dei metodi da eseguire durante la serializzazione, questi metodi sono:

  • OnSerializing, il metodo verrà chiamato prima di effettuare la serializzazione.
  • OnSerialized, il metodo chiamato appena terminata la serializzazione.
  • OnDeserializing, il metodo chiamato prima di effettuare la deserializzazione.
  • OnDeserialized, il metodo chiamato appena terminata la deserializzazione.

[csharp]
[OnSerializing()]
internal void OnSerializingMethod(StreamingContext context)
{
//Utile per impostare dei valori ad-hoc della serializzazione
cognomeLegale = "Cognome";
}

[OnSerialized()]
internal void OnSerializedMethod(StreamingContext context)
{
//utile per resettare i valori dopo la serializzazione
cognomeLegale = "";
}

[OnDeserializing()]
internal void OnDeserializingMethod(StreamingContext context)
{
//utile per impostare dei valori ad-hoc durante la deserializzazione
cognomeLegale = "Cognome";
}

[OnDeserialized()]
internal void OnDeserializedMethod(StreamingContext context)
{
//utile per resettare i valori a valle della deserializzazione
cognomeLegale = "";
}
[/csharp]

Conclusioni

Abbiamo visto che la serializzazione è quel processo per cui si vuole che delle istanze di un determinato Tipo possano essere trasformate in oggetti manipolabili dalla CRL mediante strutture generiche come stringhe e file. Abbiamo visto che vari serializzatori che ci permettono di definire il tipo di serializzazione che vogliamo usare nel nostro software: i tipi più famosi di serializzazione sono Binario, XML e JSON ottenibili con: BinaryFormatter, DataContractSerializer, XmlSerializer e DataContractJsonSerializer.

Abbiamo visto i due “modi di pensare” della serializzazione, ovvero tramite il DataContract oppure senza, infine abbiamo visto le varie “eccezioni” ovvero: JavaScriptSerializer, NetDataContractSerializer.

Ho tentato di essere quanto più sintetico ed essenziale nell’articolo, ma la mole di informazioni è notevole. Se riscontrate degli errori, vi invito ad utilizzare i commenti.

Gestire la fine del ciclo di vita degli oggetti

In questo articolo voglio illustrare la gestione della fine del ciclo di vita degli oggetti in C-Sharp.

Il nostro software gira in una porzione del sistema operativo chiamata Common Language Runtime Environment (CLR). Il ruolo di tale compente nel sistema è tener traccia di tutte le risorse proprie del software e di permettere l’accesso alle risorse esterne ad esso. Il software in C-Sharp è composto da una pletora di oggetti che (si spera) abbiano responsabilità e ruoli ben definiti.

Come avviene in Java, anche in C-Sharp un oggetto termina la sua vita all’interno del nostro software per mano del Garbage Collector (GC). Questo serial killer è la mano armata del CLR, viene chiamato in causa quando c’è scarsità di memoria e pertanto è necessaria una pulizia per fare spazio. Il GC determina quali sono gli oggetti da eliminare mediante un algoritmo di raggiungibilità di tipo “mark and compact“, ma quello che ci serve sapere è che esamina la memoria heap nel sistema al fine di trovare oggetti non più raggiungibili, se ne trova uno, lo distrugge.

Un modo gentile di procedere alla eliminazione degli oggetti è di porne i riferimenti a null, questa è la prassi consigliata. Porre a null il riferimento permette di rendere irraggiungibile l’oggetto rendendolo eligibile per il GC.

Non tutti gli oggetti hanno la stessa natura e pertanto non tutti gli oggetti si possono “smaltire” in questo modo così semplice, gli oggetti rappresentano le tipologie di risorse più varie e le risorse si suddividono in due grandi gruppi:

  • Risorse Gestite, ovvero le risorse che utilizzano gli oggetti direttamente controllabili dal framework e quindi di cui si può conoscere immediatamente lo stato, ad esempio un ArrayList è una risorsa gestita.
  • Risorse non gestite, ovvero le risorse che utilizzano oggetti non direttamente controllabili dal framework, esterne al software, l’esempio tipico di questa categoria sono i File. 

Il framework .NET tiene a cuore che il buon sviluppatore tenda ad ottimizzare l’uso della memoria, o almeno a farne un utilizzo criteriato delle risorse disposizione, pertanto ha creato un modello di utilizzo delle risorse chiamato disposable pattern e un’interfaccia ad-hoc, la IDisposable, per agevolare gli sviluppatori alla gestione corretta della memoria.

L’interfaccia IDisposable mette a disposizione il solo metodo Dispose.

L’utilizzo dell’interfaccia IDisposable è fortemente consigliato quando si utilizzano risorse non gestite, ma in generale si può implementare anche per liberare risorse gestite dalla CLR, inoltre l’implementazione dell’interfaccia IDisposable permette di utilizzare il costrutto Using che rende naturale il concetto di instanziazione e dismissione dell’oggetto utilizzato: infatti l’ultima istruzione chiamata al termine del costrutto Using è il metodo dispose.

Da qui capiamo che implementando IDisposable il CLR non chiama “da sé” il metodo Dispose per dismettere l’oggetto, bensì chiama un altro metodo, il Finalizzatore.

Il finalizzatore è chiaramente collegato al discorso dell’interfaccia IDisposable, ma interrompiamo un momento il discorso sull’interfaccia per comprenderne meglio il ruolo di questa funzione nel nostro software.

Il finalizzatore è una funzione particolare, ha il compito di distruggere l’oggetto e non può essere chiamato direttamente dal codice ma solo dal GC. Infine il finalizzatore non può essere dichiarato public static. 

Il codice del finalizzatore da un primo sguardo sembra un costruttore, infatti ha una sintassi simile se non per il fatto che prima del nome della classe è presente una tilde.

[csharp]
~Persona()
{
//qui chiamiamo il codice per finalizzare l’oggetto
}[/csharp]

Nel Finalizzatore bisogna inserire il codice che elimina solo le risorse non gestite.
Il motivo è immediato: se viene chiamato il finalizzatore dell’oggetto vuol dire che dev’essere dismesso e che tutte le risorse collegate dall’oggetto sono già state distrutte.

Da quello che abbiamo appena riportato, va da sé che un possibile codice che effettua la distruzione dell’oggetto potrebbe essere:

[csharp]
public void Dispose(){
dispose(true);
System.GC.SuppressFinalize(this);
}

public void Dispose(bool managedResources){
if(managedResources){
//liberiamo le risorse gestite
liberaRisorseGestite();
}
//liberiamo le risorse non gestite
liberaRisorseNonGestite();
}
}

~Persona(){
Dispose(false);
}
[/csharp]

Il metodo System.GC.SuppressFinalize(this) permette di rimuovere l’oggetto dalla lista di Finalizzazione degli oggetti, poiché il passaggio del GC sarebbe inutile, visto che è stato già distrutto.
Nel caso dell’utilizzo del nostro oggetto nel costrutto Using avviene che al termine dell’utilizzo l’oggetto verrà distrutto. Mentre se sarà il GC a distruggere il nostro oggetto, allora verranno dismesse le sole risorse non gestite, poiché solo quelle risorse non possono essere cancellate in autonomia dal CLR.

Pertanto come dobbiamo comportarci nell’implementare l’interfaccia?

  • Se abbiamo solo delle risorse non gestite dobbiamo implementare l’interfaccia IDisposable e il finalizzatore.
  • Se abbiamo solo delle risorse gestite dobbiamo implementare solo l’interfaccia IDisposable.
  • Se abbiamo delle risorse gestite e non gestite dobbiamo implementare l’interfaccia IDisposable e il finalizzatore.

Ultima nota, tutt’altro che marginale è che chiamare due volte il dispose, non deve causare il sollevamento di eccezioni!!!!