Application Requirements - 2

by Sandro Vecchiarelli 20. December 2011 03:47

Il punto 2.10.7 recita testualmente così: 

Your application must make a privacy policy available to users that informs them about how location data from the location service API is used and disclosed and the controls that users have over the use and sharing of location data.

Cosa vuol dire? In pratica signfica che siccome la vostra applicazione fa uso di API per la geolocalizzazione, allora dovete permettere all'utente di essere informato su come tratterete i suoi dati per rispettare la privacy. La cosa è assolutamente lecita e va nella corretta direzione del buonsenso. Il problema è che a volte tale requisito sembra inutile visto che la vostra applicazione non memorizza alcunchè da un punto di vista di localizzazione. In effetti anche se apparentemente non sfruttate la posizione dell'utente, se si usano però le API che rientrano nel namespace Microsoft.Phone.Controls.Maps allora siamo costretti ad inserire una policy per la gestione della privacy.

Pushpin

Il solo inserimento del controllo Pushpin causa un utilizzo del namespace citato sopra.

La policy che ho utilizzato e che vi mostro di seguito ha passato i controlli per cui se volete potete riutilizzarla nelle vostre applicazioni:

E' evidente che le parti offuscate riportano il nome della società proprietaria dell'applicazione. Ci sono comunque alcune attenzioni da portare nella realizzazione dell'architettura richiesta. La policy deve essere visibile immediatamente all'apertura dell'applicazione e deve partire ovviamente in modale per costringere l'utente a scegliere un'opzione. Attenzione!!!! Ho utilizzato uno sfondo nero fisso con testo bianco, perchè altrimenti se viene cambiato tema del dispositivo, si rischia che il testo non sia visibile. Deve essere sempre presente la possibilità di modificare la policy. Attenzione 2!!!!! se alla presenza della finestra modale di policy si preme il tasto Back, NON si deve uscire, ma semplicemente deve sparire la finestra e far continuare l'utente come se non avesse accettato la policy, pena il fallimento della certificazione. Tutto ciò è spiegato nei requirements di Microsoft. E' fondamentale inoltre memorizzare le impostazioni scelte dall'utente e inserirle in IsolatedStorage.

OnNavigatedTo

Per riassumere vi ifaccio vedere il codice del metodo OnNavigatedTo

 

Come risulta chiaro prima si controlla che già non ci sia una policy impostata nell'Isolation storage. Se c'è si controlla quali sono le impostazioni dell'utente (True o False vuol dire naturalmente ha accettato o meno la policy) e si abilitano o disabilitano i bottoni che portano alle pagine di utilizzo dele mappe. Se non c'è allora si rende visibile il Policy Container per far scegliere l'utente. Il Policy container rimane collapsed quando la scelta è stata già fatta.

Buon codice a tutti!

Tags: , ,

WP7

Application Certification Requirement - 1

by Sandro Vecchiarelli 11. October 2011 03:30

Chi di voi avuto la possibilità di pubblicare una applicazione sul'App Hub avrà sicurament incontrato (perlomeno inizialmente) una certa difficoltà nel passare il procedimento stesso di certificazione. Niente paura è tutto normale. Per questo motivo ho deciso di mettere a disposizione alcuni miei...errori... per poter rendere la vita un pò più facile ed evitare un pò di arrabbiature oltre che tempo prezioso. Il primo caso che prendo in considerazione è l'utilizzo dell'icona che vi mostro di seguito

Si esatto la mitica flag.png che si scarica tranquillamente dal sito riportante le regole "Metro" e che quindi uno è autorizzao a pnsare di poter utilizzare. NAturalmente questo viola il principio dettato al punto 3.1 che recita testualmente così:

 

3.1 Licensed Content, Name, Logo & Trademarks

Content allowed where:

 

  • Content and application name are original or licensed.
  • Copyrighted content that is used with permission. Use of branded items (logos/trademarks) has been approved by the brand owners.
  • If an application depicts any mobile or wired telephone, handheld PDA, or any other data and voice communicator, it must be either generic or a Windows Phone device.
  • It is the application provider‟s responsibility to determine if the application provider has the right to use the chosen name, content, logos, copyright, trademarks, online services & API‟s.
In particolare si viola il secondo punto dove parla ovviamente della licenza d'uso dei loghi registrati. A questo punto non è rimasto altro che cambiare icona e rinunciare alla flag.png.

 

 

Tags: , ,

WP7

Autenticazione Web con Google - Prima parte (1/3)

by Sandro Vecchiarelli 3. April 2011 00:25

Questo post è un pò speciale visto che non tratta direttamente con tecnologia Windows Phone 7, ma considerato che per lavoro ho dovuto implementare un meccanismo di autenticazione basato su un provider Open ID (Google), mi è sembrato giusto, in pieno spirito di condivisione, mettere a disposizione della community il risultato di un paio di serate di spippolamento di codice. Veniamo subito alla questione. Se noi abbiamo un sito ASP.NET (in questo caso con paradigma WIndows Form, ma la cosa si applica pari pari a MVC 2/3) a cui vogliamo fornire un meccanismo di autenticazione, siamo costretti a doverci preoccupare di COME può fare il client a passare le credenziali al server in maniera sicura. Questo è vero soprattutto nei casi in cui si sceglie una Forms Authentication ed il sito non si trova su una intranet. Siamo costretti spesso a utilizzare una comunicazione HTTPS. Questo però comporta una serie di problematiche, tra le quali la necessità di avere ( e manutenere) un certificato lato server e di 'costringere' i client a installare tale certificato sul proprio browser. Dal punto di vista del client inoltre, vi è anche la questione di doversi 'inventare' un account per la nostra applicazione con l'onere di annoverare l'ennesima coppia di username e password nella pletora di accounts già in possesso del nostro utente. La soluzione proviene da un protocollo che ormai si è consolidato da tempo che si chiama Open ID la cui community ha pubblicato le specifiche presso http://openid.net/.

Open ID

Visto che un provider vale l'altro (da un punto di vista del protocollo ovviamente), abbiamo scelto di usare Google come Open ID Provider a cui affidare il compito di autenticare i nostri utenti. Come funziona tutto ciò. Se andiamo sul sito di Google dove parla di OpenID troveremo una immagine abbastanza illuminante che riproponiamo qui sotto

1) l'utente decide di autenticarsial nostro sito.

2) Il punto 2 riguarda il caso in cui dessimo all'utente la possibilità di scegliere con quale Provider autenticarsi. Non è il nostro caso perchè abbiamo deciso di utilizzare solo Google (nulla ci vietava cmq. di dare la possibilità di scegliere Amazon, piuttosto che Yahoo o altri)

3) A questo punto è compito della nostra applicazione andare a cercare l'end-point, cioè l'indirizzo URI esatto, dove il provider è in attesa di ricevere delle richieste di autenticazione. E' la fase di discovery che si effettua facendo una richiesta ad un indirizzo ben preciso e

4) il Provider ci risponderà con un file XRDS (una grammatica abbastanza semplice) in cui vi è indicato l'end-point che l'applicazione web dovrà interpellare per chiedere l'aiuto necessario a autenticare l'utente

5) Appena estrapolato l'end-point, ritorneremo al client una risposta con la quale

6) provocheremo un redirect dell'utente stesso verso la pagina di autenticazione vera e propria di Google. In questa risposta (che vedremo successivamente nel dettaglio) indicheremo anche la pagina alla quale il client dovrà andare dopo l'eventuale successo nell'autenticazione.

7) L'utente inserisce i sui dati come account di Google e se tutto è ok

8) viene reindirizzato da Google alla pagina di cui al punto 6) corredando la Response di una serie di informazioni quali username, language e altre informazioni varie (claims)

In questo esempio non faremo utilizzo di OAuth, il quale è un protocollo per mezzo del quale l'utente, oltre ad autenticarsi con Oid, ha l'opportunità anche di indicare all'applicazione Web a quali eventuali informazioni o servizi aggiuntivi può accedere. Nel caso specifico l'utente potrebbe autorizzare la nostra applicazione ad accedere ai suoi Google Docs o i suoi Google Spreadsheet. Anche OAuth è un protocollo open ormai consolidato le cui specifiche possono essere trovate qui.

Let's start

Facciamo l'esempio di una semplice applicazione ASP.Net che fa utilizzo di Google come Provider di Autenticazione Open ID. Vogliamo naturalmente fare in modo che all'interno del proprio sito si continui comunque ad utilizzare un eventuale Membership Provider e un Role Provider di nostra scelta. L'unico assunto che facciamo è che ovviamente il nostro utente dovrà dirci quale è il suo account (username) con il quale accede a Google. A questo punto possiamo decidere di utilizzare quello come username dell'utente anche per il nostro sito e crearlo nel nostro repository agganciato al membership provider. corrispondente. Vediamo il nostro sito come è strutturato:

 

Come si vede abbiamo una parte pubblica contenente la pagina di Login più un'altra pagina per l'inserimento (nel nostro repository) di utenti dell'applicazione e una parte riservata contenuta nella directory 'Reserved' che proteggeremo dagli user non autenticati tramite una particolare configurazione del Web.config . Diamo un'occhiata al nostro web.config. 

Prima cosa da sottolineare: usiamo ancora il modello di autenticazione asp.net basato su Forms Authentication. Indichiamo che la pagina di Login è Login.aspx e che il cookie di autenticazione sarà targato con il nome MySecureOIDWS. Indichiamo quale è il provider per la Membership che nel nostro caso è un SQL Membership provider, proprio come il Role provider. Entrambi questi ultimi puntano, tramite la stringa di connessione CredentialDB ad un Database compliant con il Membership e il Role Provider. Per chi non lo sapesse per creare un database coerente con le regole dei provider di autenticazione basta digitare il comando "aspnet_regsql" dal command prompt di Visual Studio e seguire il relativo Wizard. Il database creato è lievemente modificato aggiungendo una tabella. Vediamo come:

 

La tabella si chiama Tbl_OIDAssociation. Questa tabella ha 3 campi che indicano la mail (username) utilizzata dall'utente come account di google, l'ID univoco di Google restituito dopo il logon corrispondente all'utente con quella mail (username) ed infine l'ID corrispondente allo user dell'applicazione Web (ID_RelyingPartyUser) che è la chiave (UserId) della tabella aspnet_Membership. Il concetto è questo. Noi creiamo un utente nella tabella aspnet_Membership con username uguale alla mail che lo user utilizzerà per autenticarsi a Google. Quando l'utente si autenticherà a Google quest'ultimo ci restituirà un ID specifico per quell'utente e la mail con la quale ha fatto login e noi allora creeremo un record di associazione (appunto) che colleghi l'utente della applicazione web con l'utente Google. Infine vediamo come aggiungere i tag giusti per impedire l'accesso agli utenti non autenticati nella parte 'Reserved' e lasciare l'accesso a tutti nella parte pubblica:

Isolated Storage su Windows Phone 7

by Sandro Vecchiarelli 28. November 2010 16:38

In questo post parliamo dell'Isolated Storage, una porzione di memoria dedicata a ciascuna applicazione presente nel nostro dispositivo Windows Phone 7. Vediamo innanzitutto come viene descritto nell'help msdn la raffigurazione logica dell'Isolated Storage

Come vediamo abbiamo due tipi di Isolated Storage.Uno è una specie di File System che può essere percorso attraversando le varie meta-directory contenenti i vari meta-files. L'altro tipo di memoria è un dictionary in cui possiamo memorizzare coppie di chiave-valore. Una delle cose più interessanti da sottolineare è che non esistono 'quote' da assegnare a ciascuna applicazione. In pratica ciascuna applicazione ha a disposizione una porzione di Isolated Storage isolata appunto da tutte le altre porzioni appartenenti alle altre applicazioni. Questa porzione tuttavia non ha  un limite. In realtà esistono come al solito delle best-pratices per fare in modo da non raggiungere il 90% dell'occupazione di memoria allorchè il device si ferma e vi costringe a cancellare una part delle cosa presenti sul dvce...cosa veramente molto controindicante. Nel progetto che alleghiamo, abbiamo una lettura di dati dall'Isolated Storage e un recupero dalla stessa in maniera molto semplice.

 

BLOG_SV.rar (63,94 kb)

Tags: , ,

WP7

Ciclo di Esecuzione di una applicazione Windows Phone 7

by Sandro Vecchiarelli 28. November 2010 01:16

Una delle più importanti novità introdotte dal device Windows Phone 7 è il ciclo di esecuzione delle applicazioni end user. A differenza del precedente sistema presente in Windows Mobile, dove potevamo creare un processo e metterlo in background in parallelo con l'esecuzione di un'applicazione in foreground, adesso possiamo avere SOLO UNA applicazione in esecuzione. Questa è la regola fondamentale da tenere ben presente. Inoltre dobbiamo avere molto ben presente cosa succede ad una applicazione da quando viene lanciata a quando e come termina la sua esecuzione. Di seguito una figura presa dall'help microsoft:

Start

Partiamo dal momento in cui l'applicazione viene lanciata. Questo può accadere da differenti posizioni:  dalla pagina di Start dove risiedono le Tiles, oppure dalla lista delle applicazioni installate sul device oppure con un 'tap' sulla notifica presente in 'toast'. L'evento associato a questa fase è 'Launching' dentro il cui gestore è possibile effettuare alcune operazioni tranne quelle troppo dispendiose quali, per esempio, il recupero dei dati dall'Isolated Storage.

Running

A questo punto la nostra applicazione gira e si disegna sullo schermo del device. E' possibile a questo punto recuperare i dati dall'Isolated Storage (meglio se tramite un thread parallelo per rendere l'interfaccia più reattiva). A questo punto possono accadere 2 cose: Si preme il tasto Back sulla prima pagina del'applicazione oppure si usa una API di tipo Launcher o di tipo Chooser. Spieghiamo bene. Il Launcher è una API che lancia un'applicazione predefinita nel device e che non ritorna nessun risultato alla routine chiamante. Un esempio ne è il PhoneCallTask che serve per effettuare una chiamata. Il Chooser è analogo al Launcher con la differenza che ritorna un risultato: PhotoChooser che serve a ritornare l'immagine di una foto.

Back sulla prima pagina durante il running

Si torna al punto di partenza, l'applicazione viene definitivamente chiusa. Viene sollevato l'evento 'closing' nel cui gestore dobbiamo salvarei dati nell'Isolated Storage. Al nuovo lancio sarà una nuova istanza del programma ad andare in esecuzione.

Launcher o Chooser durante il running

Si passa in uno stato di 'Deattivata'. Ciò significa che l'applicazione è terminata e che in esecuzione c'è il Launcher oil Chooser, in perfetta sintonia con la regola descritta all'inizio. Quando l'applicazione viene disattivata il sistema può operare un 'tombstone' su di essa, con il quale si memorizza alcune informazioni sull'applicazione chiamante per far si che al momento della chiusura del Launcher o del Chooser questa venga in qualche modo riattivata. Sarà comunque sempre compito dello sviluppatore ripristinare esattamente lo stato della UI affinchè questa appaia come se fosse sempre rimasta in background.Non sempre l'applicazione subisce il 'tombstoning' del sistema, quindi tale evento essendo non deterministico costringe lo sviluppatore a salvare eventuali dati persistenti nella Isolated Storage. L'evento sollevato è il 'Deactivated'. Nel gestore di evento 'deactivated' si salvano i dati relativi alla pagina (Transient page data) e relativi al'applicazione (disponibili per tutte le pagine).

A questo punto possono accadere ancora due cose. SI ritorna alla nostra applicazione tramite il bottone di 'Back' o il Launcher (o Chooser) termina il proprio compito e il sistema riesuma l'applicazione. Viene sollevato l'evento 'Activated'

Gestione dell'Attivazione

Abbiamo due gestori di eventi da implementare. Abbiamo Application_Activated in cui riesumiamo i dati interessanti per tutte le pagine e OnNavigatedTo per riprendere i dati di transizione perla pagina corrente. Inentrambi i casi riesumiamo i dati che in precedenza erano stati messi in due oggetti di tipo Dictionary messi a disposizione dello sviluppatore e che si chamano rispettivamente PhoneApplicationSrvice.State e PhoneApplicationPage.State. Nella solution proposta vengono inseriti i progetti relativi ad alcuni esempi di salvataggio dei dati della pagina e dell'applicazione.

Di seguito la solution con due progetti per il salvataggio dei dati Application e Page:

SV_Blog.rar (176,25 kb)

Tags: , , ,

WP7

Windows Phone 7 e Fiddler

by Sandro Vecchiarelli 27. November 2010 21:05

Andando alla ricerca di documentazione troviamo vari suggerimenti interessanti su come sniffare il traffico di rete Http che passa dal nostro emulatore Windows Phone. Uno dei migliori sniffer Http in circolazione (e che viene utilizzato ormai in moltissimi speech Microsoft) è Fiddler, un Open Source veramente notevole le cui potenzialità (non solo di ispezione traffico) sono veramente notevoli. La curiosità di usarlo su WP7 Emulator diventa quindi molto forte, per cui se volete provare questo strumento non avete da fare nient'altro che scaricarvi Fiddler dal sito ufficiale. Dopo esserci installato Fiddler scarichiamoci l'update di fiddler per windows phone che potete trovare qui:

Fiddler2Setup.rar (585,47 kb)

Come in qualsiasi altro sniffer, c'è il problema che non si riesce a catturare il traffico passante dalla scheda di loopback (così come in Wireshark). Per aggirare questo problema esistono diversi workaround, ma il più efficace di tutti è quello indicato sul sito ufficiale sostituendo localhost con l'indirizzo ipv4.fiddler. Esiste comunque una documentazione FAQ che risponde in maniera esaustiva a questo problema. A questo punto è necessario intervenire sulle impostazioni. Fate partire Fiddler e dal menù principale andate in Tools > Fiddler Options e nel Tab Connections selezioniamo"Allow remote computers to connect". Dopo l'OK e la chiusura delle opzioni andiamo nella QuickExec box (quella in fondo a sinistra...una specie di command prompt) e digitiamo il seguente comando:

Sostituite ovviamente MYCOMPUTERNAME con il nome della vostra machina desktop. A questo punto chiudete e fate ripartire Fiddler. Lanciate WP7 Emulator e provate a navigare in internet oppure a navigare sui siti della vostra macchina sostituendo 127.0.0.1 o localhost con ipv4.fiddler, l'effetto sarà davvero notevole. Se avevate l'Emulator WP7 aperto è molto probabile che dobbiate riavviarlo.

 

Windows Phone 7 e WCF Data Service (Odata)

by Sandro Vecchiarelli 27. November 2010 19:42

na cosa che sicuramente sarà accaduta a molti è quella di avere un pò di confusione sui termini Data Service, Domain Service, RIA Service e così via. C'è un bellissimo articolo di Riccardo Di Nuzzo che è davvero illuminante per diramare i notevoli dubbi che inevitabilmente sorgono confrontando i vari termini citati sopra. Innanzitutto quello che verrà illustrato in questo post sarà la possibilità di consumare un Data Service OData tramite un client Windows Phone 7. Partiamo creando un nuovo Sito Web con posizione su localhost (importante che sia su localhost per il motivo che vedremo avanti). Nell'esempio si è usata la locazione C:\inetpub\wwwroot\NWDataService e il servizio lo abbiamo chiamato NWDataService.svc. Togliamo dal sito web tutti i file prettamente Web (lasciando solo il web config, ilglobal.asax, la cartella App_Code e la cartella App_Data. Aggiungiamo quindi un nuovo modello dati che chaimeremo NorthWind.edmx (diamo per scontato che un Northwind DB c'è sicuramente sulle macchine di tutti gli sviluppatori altrimenti è semplicissimo procurarselo). Ai fini di questo progetto abbiamo inserito solo la tabella Employees. Per completare il tutto aggiungiamo un WCF DataService e come modello di Entities da assegnare scegliamo ovviamente il nostro NorthWind.edmx. Il progetto Web dovrebbe apparire così:

Per provare se il servizio funziona, di solito si fa click con il tasto destro sul file .svc e si sceglie 'view in browser'. Questo farà partire immediatamente la richiesta al server che ci ritornerà un 'riassunto' delle entities che potremo interrogare.

Infatti i dataservices pubblicano delle entità che è possibile interrogare tramite il protocollo OData. Quest'ultimo è un formalismo divenuto oramai uno standard molto utilizzato che permette di inviare delle query al servizio WCF che espone delle entities. Alcuni esempi di query Odata possono essere consultate nel sito ufficiale

WCF Data Service Client

Adesso andiamo a costruire il progetto Silverlight Windows Phone che dovrà consumare il servizio appena implementato. Appena aperto il progetto la prima cosa che viene da fare è quella di fare un bel 'add service reference', ma a quel punto andremmo incontro ad una delusione in quanto tale reference non è supportata da Windows Phone. La riprova la abbiamo utilizzando Fiddler, lo sniffer Http che in questo post abbiamo imparato ad utilizzare con l'emulatore di Windows Phone 7. Dopo aver mandato in esecuzione Fiddler effettuiamo la richiesta della risorsa svc e notiamo quanto accade:

Esatto il file non può essere consumato in maniera nativa da WP7. Se andiamo a vedere il tracciato di Fiddler notiamo come la richiesta Http avvenga correttamente, così come la risposta del server sia ineccepibile. Il problema è proprio la compatibilità di OData con WP7.

Fortunatamente è possibile però arrivare al risultato che ci aspettiamo sfruttando una libreria, che è possibile scaricare da qui 

ODataLibrary_WinPhone7_CTP.rar (343,38 kb)

Scompattate la libreria e memorizzatevi la directory dove andrete a mettere l'assembly che ci interessa chiamato System.Data.Services.Client.dll di cui dobbiamo aggiungere un riferimento nel nostro progetto WP7. Dopo aver fatto l'add reference della libreria client dobbiamo costruirci quelle classi che rappresentano il client vero e proprio relativo al servizio svc. Dobbiamo cioè costruire quelle classi che sul lato client invocheranno (tramite le classi presenti in System.Data.Services.Client) le corrette query OData e saranno in grado di ricostruire sul dispositivo le collections di oggetti relativi alle response ricevute. Per fare questo dobbiamo posizionarci sulla directory C:\Windows\Microsoft.NET\Framework\v4.0.30319 (se il vostro S.O è in C:\Windows altrimenti inserite la vostra SystemRoot directory). Digitare quindi il seguente comando:

DataSvcutil.exe /uri:http://localhost/NWDataService/NWDataService.svc/ /DataServiceCollection /Version:2.0 /out:C:\Users\sandro\Documents\ClientCode\NWClient.cs

Indicate ovviamente la directory di out dove memorizzare gli 'artifacts' di tipo client. Copiate il file NWCliwnt.cs fisicamente nella directory del progetto e aggiungetelo come 'exsting item'. A questo punto il più è fatto, non rimane che costruire il servizio-client che metteremo dentro una classe NorthWindService. La classe NorthWindService ha un metodo, QueryEmployees, il quale crea prima di tutto un 'context' a partire da una Uri che punta al nostro file svc. L'indirizzo localhost lo abbiamo sostituito con quello riconosciuto da Fiddler così da poterlo catturare successivamente.

Il risultato viene consumato nella callback. Possiamo vedere il traffico che è passato tra l'emulatore e localhost e notiamo che è la stessa richiesta fatta in precedenza, solo che stavolta, tramite la libreria utilizzata, siamo riusciti a consumare la risposta pervenutaci dal service.

Da notare solamente due cose. La prima, come indicato dalla freccia, la richiesta Http è stata inoltrata a 127.0.0.1 e la seconda è che il client ha interrogato il server secondo protocollo OData, chiedendo Atom e ottenendo esattamente questo come si vede anche dalla risposta data dal server.

Soprattutto...enjoy the code:

SV_Blog.rar (938,53 kb)

Tags: , , , ,

WP7

Sandro Vecchiarelli

MeLaureato in Scienze dell'informazione a Pisa, si è occupato di analisi e progettazione software per istituti bancari e pubbliche amministrazioni, approfondendo inoltre gli aspetti della programmazione ad oggetti sin dai primi esordi del web in Italia. Certificato MCSE e MCT si occupa anche di elementi legati al networking e alla sicurezza. Svolge un' importante attività di docenza su argomenti riguardanti la piattaforma .NET, XML e Web services. E' autore di 2 corsi di successo: "Windows Server Security"(MIE1311), riguardante gli aspetti legati alla sicurezza della piattaforma Windows Server, e "Ingegneria del software: tecniche per la costruzione di software di qualità" (MIEAU36) dove vengono affrontate le tecniche per scrivere codice il più possibile robusto, riusabile e performante. Negli ultimi anni ha acquisito anche importanti competenze riguardanti prodotti molto complessi quali Microsoft Biztalk Server, oltre ad aver affrontato l'analisi e lo sviluppo di applicazioni in ambienti embedded quali Windows CE e Pocket PC. Nel 2008 è stato nominato MVP Microsoft Windows Mobile, riconfermato nel 2009, 2010 e 2011.

1 2 3 4 5 6 7 8

Tag cloud

Page List