Guest post: Integrare Facebook nelle Universal Windows app


 

Questo post è stato scritto da Matteo Pagani, Support Engineer in Microsoft per il programma AppConsult.

Un requisito piuttosto frequente, soprattutto nelle applicazioni mobile, è l’integrazione con servizi di terze parti: uno dei più utilizzati è Facebook, grazie al quale possiamo autenticare l’utente, semplificare la condivisione di contenuti sul popolare social network o recuperare informazioni sul suo profilo.

L’autenticazione con Facebook (e, più in generale, con molti altri servizi di terze parti) è basata su un protocollo chiamato oAuth, che permette di garantire all’utente finale un certo livello di sicurezza: tramite questo meccanismo, infatti, l’utente non dovrà mai immettere le sue credenziali direttamente nella nostra applicazione (dandoci la possibilità, quindi, di scoprire i suoi dati di accesso), ma si autenticherà tramite una schermata web gestita direttamente dal fornitore del servizio stesso. Quello che, a noi sviluppatori, sarà restituito è un access token, ovvero una chiave (assolutamente anonima e che non consente in alcun modo di risalire alle credenziali dell’utente) che sarà utilizzata per effettuare e autenticare le operazioni successive.

Nel corso di questo post vedremo come, grazie alla libreria Facebook SDK for .NET e alla classe WebAuthenticationBroker introdotta nel Windows Runtime, saremo in grado di integrare Facebook all’interno della nostra Windows Store app per Windows e Windows Phone in maniera semplice: dopo che l’utente si sarà autenticato con le sue credenziali, avremo la possibilità di sfruttare le Graph API di Facebook per interagire con il suo profilo e le informazioni da lui esposte sul social network.

Step 1: registrare l’applicazione sullo Store

Per poter integrare l’autenticazione tramite Facebook, è necessario innanzitutto registrare l’applicazione sullo Store di Windows, riservandone un nome. Questa procedura consente di associare alla vostra applicazione un nome univoco, che sarà esclusivo per lo Store: nessuna altra applicazione pubblicata successivamente potrà utilizzarlo. Se non lo avete ancora fatto in precedenza (ad esempio, perchè non avete ancora pubblicato l’applicazione), è possibile farlo cliccando con il tasto destro sul progetto (Windows o Windows Phone) in Visual Studio, scegliere la voce Store e l’opzione Associate App with Store. Visual Studio darà inizio ad una procedura guidata, che vi permetterà di associare la vostra applicazione ad un nome che avete già riservato in precedenza, oppure di riservare direttamente un nuovo nome. In entrambi i casi, la procedura si farà carico di aggiornare il file di manifest del vostro progetto, così da collegare l’applicazione allo Store.

Una volta registrata l’applicazione, è necessario collegarsi alla dashboard di Windows (https://appdev.microsoft.com/StorePortals/en-us/Home/Index), indipendentemente dal fatto che si stia pubblicando un’applicazione Windows o Windows Phone. Questo perchè l’informazione che ci serve (che andremo a recuperare in questo step) è valida per entrambe le piattaforme ma, al momento della stesura di questo post, è recuperabile solo dalla dashboard di Windows. Nota bene! Questo significa che, anche nel caso in cui vogliate pubblicare solo la versione Windows Phone della vostra applicazione, dovrete comunque procedere a registrarle su entrambi gli Store, riservandone lo stesso nome, come se voleste pubblicare una Universal app (ovvero due applicazioni per Windows e Windows Phone che condividono lo stesso nome e la stessa identità, come spiegato nella documentazione MSDN all’indirizzo https://msdn.microsoft.com/en-us/library/windows/apps/dn646927.aspx).

Una volta collegati alla dashboard con il vostro account sviluppatori, potete dare inizio alla procedura di invio di una nuova applicazione (o di caricamento di un nuovo aggiornamento, nel caso vogliate integrare Facebook in un’applicazione già esistente).

La procedura di certificazione si compone di diversi passaggi: nel corso di questo post non andremo a completarli tutti, dato che non pubblicheremo effettivamente l’applicazione sullo Store. Per proseguire e abilitare l’accesso agli step successivi, è indispensabile completare il primo step, chiamato App name, in cui ci viene richiesto il nome dell’applicazione: dovremo scegliere, dall’apposito menu a tendina, lo stesso nome che abbiamo riservato da Visual Studio nel passaggio precedente.

clip_image002

Dopo aver confermato premendo il pulsante Continue, torneremo all’elenco degli step necessari per completare la pubblicazione: possiamo passare direttamente a quello che ci interessa per il nostro scopo, ovvero il terzo, chiamato Services:

clip_image004

Cliccandoci sopra, apparirà la seguente schermata:

clip_image006

In figura trovate evidenziato il link che dovrete cliccare, ovvero la voce Live Services site. Si aprirà la pagina seguente che, tra le varie informazioni, mostrerà il Package SID (evidenziato nell’immagine seguente), che è un URL che identifica univocamente la nostra applicazione. E’ l’informazione che ci servirà per configurare correttamente l’integrazione con Facebook: annotatelo, perché lo utilizzeremo nel passaggio successivo.

clip_image008

 

Step 2: registrare l’applicazione su Facebook

Il secondo passaggio è registrare l’applicazione sul portale sviluppatori di Facebook (https://developers.facebook.com). Si tratta del portale in cui vengono registrate tutte le applicazioni che interagiscono con il social network, siano esse applicazioni mobile, web, desktop, ecc. La registrazione, infatti, vi dà accesso ad una serie di credenziali che vi consentono di autenticare la vostra applicazione in maniera sicura. Il primo passaggio è, dopo aver fatto login con il vostro account Facebook, registrare una nuova applicazione, scegliendo dal menu in alto My apps la voce Add a new app.

clip_image009

Facebook vi chiederà la piattaforma di riferimento: scegliete la voce Advanced platform, dato che Windows e Windows Phone non saranno immediatamente disponibili nell’elenco. Comparirà una schermata come la seguente:

clip_image011

Inserite tutte le informazioni base (nome e categoria), dopodiché procedete alla creazione tramite il pulsante Create App ID.

Una volta completata la configurazione, dovrete procedere a configurare una serie di parametri fondamentali. I primi si trovano nella sezione Settings, accessibile dal menu di sinistra, all’interno della quale dovrete:

 

  • Valorizzare il camp Contact email con una mail (reale) di contatto. Senza questa informazione, non sarete in grado di rendere la vostra applicazione Facebook pubblica (e quindi la vostra Windows Store app non sarà in grado di interagirvi).
  • Cliccare sul pulsante Add platform per aggiungere il supporto ad una nuova piattaforma: qui troverete, al contrario di prima la voce Windows, che aggiungerà una sezione con due campi, uno chiamato Windows Store ID e uno Windows Phone Store ID [BETA]. Per l’approccio che stiamo seguendo in questo post (ovvero l’autenticazione tramite la classe WebAuthenticationBroker, di cui parleremo più avanti) è sufficiente impostare il campo Windows Store ID: trattandosi di una Universal Windows app, la configurazione sarà valida sia per Windows che per Windows Phone. L’informazione da inserire all’interno di questo campo è il Package SID che abbiamo recuperato in precedenza, all’interno del terzo step della procedura di certificazione. Dobbiamo solo eliminare il protocollo, ovvero ms-app://, come potete vedere nell’immagine seguente.

 

clip_image013

A questo punto possiamo rendere pubblica l’applicazione Facebook, così da potervi interagire dalla nostra Windows Store app: lo facciamo nella sezione Status & Review, impostando su Yes l’opzione Do you want to make this app and all its live features available to the general public?

clip_image015

 

Step 3: autenticarsi dall’applicazione

Ora che abbiamo configurato l’applicazione sia sulla Dashboard di Windows che su quella di Facebook, possiamo integrare l’autenticazione direttamente nella nostra applicazione. Innanzitutto è necessario aggiungere la libreria Facebook C# SDK a entrambi i progetti (Windows e Windows Phone), disponibile su NuGet (https://www.nuget.org/packages/Facebook/6.8.0).

Dopodichè andremo ad usare una classe offerta dal Windows Runtime chiamata WebAuthenticationBroker: il suo scopo è quello di semplificare l’autenticazione verso i servizi che fanno uso di oAuth come meccanismo di sicurezza. Tipicamente, quando lavoriamo con oAuth, come sviluppatore dobbiamo farci carico di una serie di operazioni piuttosto tediose, quali:

  1. Generare l’URL di autenticazione verso il servizio.
  2. Includere una WebView all’interno della nostra applicazione, per mostrare la pagina di autenticazione del servizio.
  3. Una volta che l’utente ha effettuato il login, intercettare l’URL di risposta del servizio e, se tutto è andato a buon fine, recuperare l’access token.

La classe WebAuthenticationBroker fa tutto questo per noi: riceve, in ingresso, l’URL e i parametri necessari per l’autenticazione oAuth e si fa carico di generare la vista web, in cui l’utente dovrà inserire le sue credenziali: dopodiché, terminata la procedura, riceveremo direttamente l’access token necessario per effettuare qualsiasi altra operazione con i servizi di Facebook.

La procedura di configurazione base della classe WebAuthenticationBroker è comune su entrambe le piattaforme: la fase successiva, però, si differenzia, in quanto Windows e Windows Phone hanno una gestione differenze.

 

La configurazione iniziale

Ecco un esempio di definizione della procedura di configurazione:

 

privateasyncTask Login()

{

    //Id dell’applicazione Facebook

    var clientId = “1531845183735056”;

    //I permessi di accesso di Facebook

    var scope = “public_profile, email”;

 

    var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();

    var fb = newFacebookClient();

    Uri loginUrl = fb.GetLoginUrl(new

    {

        client_id = clientId,

        redirect_uri = redirectUri,

        response_type = “token”,

        scope = scope

    });

 

    Uri startUri = loginUrl;

    Uri endUri = newUri(redirectUri, UriKind.Absolute);

}

Innanzitutto è importante evidenziare l’uso di due variabili:

 

  • clientId è l’id dell’applicazione Facebook, che ci viene restituito dal portale sviluppatori di Facebook, all’interno della sezione Dashboard, come evidenziato nella figura sottostante:

clip_image017

 

Dopodiché dobbiamo recuperare l’URL di callback, che viene chiamato da Facebook una volta completata l’operazione di login: tale URL coincide con il Package SID che abbiamo recuperato in precedenza durante la procedura di certificazione. Non abbiamo bisogno, però, di specificarlo manualmente, in quanto ce lo offre comodamente il metodo GetCurrentApplicationCallbackUri() della classe WebAuthenticationBroker.

Infine possiamo creare una nuova istanza della classe FacebookClient (che fa parte del pacchetto che abbiamo installato tramite NuGet) e chiamare il metodo GetLoginUrl(), passando come parametro un nuovo oggetto che contiene tutti i parametri necessari per l’autenticazione, ovvero client_id (l’id dell’applicazione Facebook), redirect_uri (l’URL di callback), response_type (è un valore fisso, deve essere token) e lo scope, ovvero i permessi. A questo punto il metodo GetLoginUrl() ci restituirà l’URL da chiamare per avviare la procedura di autenticazione.

 

Gestire l’autenticazione in Windows 8.1

Gestire l’autenticazione in Windows 8.1 è molto semplice, in virtù del fatto che il sistema operativo è in grado di mantenere aperte più applicazioni contemporaneamente:

 

privateasyncTask Login()

{

    //Id dell’applicazione Facebook

    var clientId = “1531845183735056”;

    //I permessi di accesso di Facebook

    var scope = “public_profile, email”;

 

    var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();

    var fb = newFacebookClient();

    var loginUrl = fb.GetLoginUrl(new

    {

        client_id = clientId,

        redirect_uri = redirectUri,

        response_type = “token”,

        scope = scope

    });

 

    Uri startUri = loginUrl;

    Uri endUri = newUri(redirectUri, UriKind.Absolute);

 

    WebAuthenticationResult result = awaitWebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri, endUri);

    await ParseAuthenticationResult(result);

 

}

É sufficiente chiamare il metodo AuthenticateAsync() della classe WebAuthenticationBroker, passando come parametri il tipo di autenticazione (in questo caso, None) e i due URL costruiti in precedenza (login e callback). Otterremo, in ritorno, un oggetto di tipo WebAuthenticationResult, con le informazioni sull’esito dell’operazione e l’URL di risposta di Facebook. Vedremo successivamente come funziona il metodo ParseAuthenticationResult(), che si fa carico di elaborare la risposta di Facebook.

clip_image019

 

Gestire l’autenticazione in Windows Phone 8.1

Gestire l’autenticazione in Windows Phone 8.1 è leggermente più complicato, in quanto Windows Phone non è in grado di mantenere più applicazioni aperte contemporaneamente, ma deve sospendere l’applicazione principale nel momento in cui delega alla vista web la procedura di autenticazione.

Anzichè chiamare il metodo AuthenticateAsync(), che ci restituisce direttamente il risultato dell’operazione, dobbiamo chiamare il metodoAuthenticateAndContinue(), come nell’esempio seguente:

 

privateasyncTask Login()

{

    //Id dell’applicazione Facebook

    var clientId = “1531845183735056”;

    //I permessi di accesso di Facebook

    var scope = “public_profile, email”;

 

    var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();

    var fb = newFacebookClient();

    var loginUrl = fb.GetLoginUrl(new

    {

        client_id = clientId,

        redirect_uri = redirectUri,

        response_type = “token”,

        scope = scope

    });

 

    Uri startUri = loginUrl;

    Uri endUri = newUri(redirectUri, UriKind.Absolute);

 

    //Avvio l’operazione di autenticazione

    WebAuthenticationBroker.AuthenticateAndContinue(startUri, endUri, null, WebAuthenticationOptions.None);

 

}

Come è possibile notare, in questo caso non riceviamo direttamente il risultato dell’autenticazione: questo perchè, chiamando questo metodo, l’applicazione sarà sospesa, per lasciare spazio alla pagina di autenticazione di Facebook.

clip_image021

Il passo successivo è quello di includere, all’interno del nostro progetto, una classe chiamata ContinuationManager, che semplifica il codice da scrivere. E’ possibile trovare la definizione di tale classe su http://msdn.microsoft.com/en-us/library/windows/apps/xaml/dn631755.aspx oppure nel progetto di esempio legato a questo post.

Una volta aggiunta la classe nel nostro progetto, dobbiamo gestire l’attivazione e la sospensione dell’applicazione all’interno della classe App dichiarata nel file App.xaml.cs (che fa parte del progetto condiviso della Universal Windows app).

 

publicsealedpartialclassApp : Application

{

    publicstaticContinuationManagerContinuationManager{get;private set; }

 

 

    public App()

    {

        this.InitializeComponent();

        this.Suspending += this.OnSuspending;

        ContinuationManager = newContinuationManager();

    }

 

 

    privatevoid OnSuspending(object sender, SuspendingEventArgs e)

    {

        var deferral = e.SuspendingOperation.GetDeferral();

 

        ContinuationManager.MarkAsStale();

 

        deferral.Complete();

    }

 

    protectedoverridevoid OnActivated(IActivatedEventArgs args)

    {

        if (args.Kind ==ActivationKind.WebAuthenticationBrokerContinuation)

        {

            varcontinuationEventArgs=argsasIContinuationActivatedEventArgs;

            if (continuationEventArgs != null)

            {

                ContinuationManager.Continue(continuationEventArgs);

                ContinuationManager.MarkAsStale();

            }

 

        }

    }

}

In evidenza è possibile vedere le parti che sono state modificate rispetto al codice originale della classe:

 

  • Abbiamo dichiarato una nuova proprietà di tipo ContinuationManager.
  • Nel costruttore della classe App, creiamo una nuova istanza della classe.
  • All’interno del metodo OnActivated(), verifichiamo se l’applicazione è stata riattivata in seguito all’utilizzo del WebAuthenticationBroker: lo facciamo verificando che la proprietà Kind sia tipo ActivationKind.WebAuthenticationBrokerContinuation. In questo caso, chiamiamo il metodo Continue() della classe ContinuationManager, passando come parametro il parametro dell’evento OnActivated(). Dopodichè chiamiamo il metodo MarkAsStale(), che si assicura che non rimangano in memoria dati già utilizzati e quindi non più validi.
  • Per lo stesso motivo, facciamo lo stesso in fase di sospensione dell’applicazione, all’interno del metodo OnSuspending().

Quando chiamiamo il metodo Continue() della classe ContinuationManager, l’utente viene riportato alla pagina che ha invocato il metodo AuthenticateAndContinue() della classe WebAuthenticationBroker: il passo successivo è quello di implementare, nel code behind della pagina, l’interfaccia IWebAuthenticationContinuable, che ci richiederà di implementare il metodo ContinueWebAuthentication(): si tratta del metodo che viene invocato quando la pagina viene riaperta in seguito ad una procedura di autenticazione. Ecco un esempio:

 

publicsealedpartialclassMainPage : Page, IWebAuthenticationContinuable

    {

 

        public MainPage()

        {

            this.InitializeComponent();

 

            this.NavigationCacheMode = NavigationCacheMode.Required;

        }

 

   publicvoid ContinueWebAuthentication(WebAuthenticationBrokerContinuationEventArgs args)

   {

       await ParseAuthenticationResult(args.WebAuthenticationResult);

   }

    }

E’ possibile notare come l’elaborazione del risultato dell’autenticazione venga effettuata all’interno del metodo ContinueWebAuthentication(): il parametro del metodo, infatti, contiene una proprietà di nome WebAuthenticationResult, che contiene le informazioni ricevute da Facebook.

 

Elaborare il risultato

Indipendentemente dalla piattaforma su cui stiamo sviluppando, quello che otteniamo in ritorno dall’utilizzo della classe WebAuthenticationBroker è un oggetto di tipo WebAuthenticationResult. Ecco un esempio di come elaborare tale oggetto per estrarre le informazioni che ci servono:

 

publicasyncTask ParseAuthenticationResult(WebAuthenticationResult result)

{

    switch (result.ResponseStatus)

    {

        caseWebAuthenticationStatus.ErrorHttp:

            Debug.WriteLine(“Error”);

            break;

        caseWebAuthenticationStatus.Success:

            var pattern = string.Format(“{0}#access_token={1}&expires_in={2}”, WebAuthenticationBroker.GetCurrentApplicationCallbackUri(), “(?<access_token>.+)”, “(?<expires_in>.+)”);

            var match = Regex.Match(result.ResponseData, pattern);

 

            var access_token = match.Groups[“access_token”];

            var expires_in = match.Groups[“expires_in”];

 

            AccessToken = access_token.Value;

            TokenExpiry = DateTime.Now.AddSeconds(double.Parse(expires_in.Value));

 

            break;

        caseWebAuthenticationStatus.UserCancel:

            Debug.WriteLine(“Operation aborted”);

            break;

        default:

            break;

    }

}

L’oggetto WebAuthenticationResult contiene una proprietà di nome ResponseStatus che ci indica l’esito dell’operazione: solo in caso sia WebAuthenticationStatus.Success significa che Facebook ci ha restituito l’URL di autenticazione che contiene, tra le altre cose, l’access token e la data di scadenza (trascorsa la quale è necessario richiederne uno nuovo). L’esempio di codice mostra come recuperare queste due informazioni dall’URL (contenuto all’interno della proprietà ResponseData) e salvarle all’interno di due apposite variabili.

Una volta in possesso dell’access token, è possibile utilizzare le API dell’SDK di Facebook per interagire con i servizi del social network, come nell’esempio seguente:

 

privateasyncTask ShowUserInfo()

{

    FacebookClient client = newFacebookClient(AccessToken);

    dynamic user = await client.GetTaskAsync(“me”);

    MyName.Text = user.name;

}

Il cuore è la classe FacebookClient (che abbiamo già visto in precedenza per recuperare l’URL di login): questa volta, però, possiamo passare come parametro del costruttore l’access token appena recuperato, così da autenticarci correttamente. A questo punto possiamo interagire con le Graph API di Facebook, che espongono una serie di metodi per interagire con i vari servizi del social network (trovate la reference completa dei metodi disponibili su https://developers.facebook.com/docs/graph-api/reference). La classe FacebookClient funziona in maniera simile alla classe HttpClient del Windows Runtime ed espone una serie di metodi per effettuare le operazioni più comuni esposte dal protocollo HTTP, come GET, POST, ecc.

Ad esempio, se vogliamo recuperare le informazioni sul profilo dell’utente loggato, dobbiamo effettuare una GET, invocando il comando me: per questo scopo, usiamo il metodo GetTaskAsync(), passando come parametro il comando delle Graph API da eseguire. Ci viene restituito un oggetto che contiene i vari parametri della risposta: il modo più semplice è utilizzare la keyword dynamic, che ci permette di lavorare con un oggetto dinamico e non fortemente tipizzato. Lo svantaggio è che non potremo usare l’Intellisense (dato che l’oggetto user dell’esempio non è un’istanza di una classe specifica), ma potremo semplicemente accedere alle proprietà semplicemente specificandone il nome, che possiamo recuperare nella documentazione. Ad esempio, se guardiamo i dettagli dell’oggetto user delle Graph API (https://developers.facebook.com/docs/graph-api/reference/v2.2/user), scopriremo che il nome completo dell’utente è memorizzato all’interno della proprietà name: grazie all’oggetto di tipo dynamic, possiamo recuperarla semplicemente usando la sintassi user.name.

Se, invece, avessimo voluto pubblicare un contenuto sul profilo dell’utente, si sarebbe trattata di un’operazione di tipo POST: in questo caso, avremmo dovuto usare il metodo PostTaskAsync() esposto dalla classe FacebookClient.

 

Gestire l’autenticazione in una Universal Windows app

Gli esempi di codici visti fin qui presupponevano l’utilizzo di due classi e due pagine separate per le due piattaforme Windows e Windows Phone, per via delle differenze nell’utilizzo della classe WebAuthenticationBroker. Nel caso in cui vogliate gestire, invece, questo scenario all’interno di una classe o di una pagina all’interno del progetto condiviso, dovrete utilizzare la compilazione condizionale, ovvero sfruttare i simboli di compilazione WINDOWS_APP e WINDOWS_PHONE_APP per far sì che lo specifico codice venga compilato solo sulla relativa piattaforma.

Questo perché i metodi specifici non sono disponibili su entrambe le piattaforme: ad esempio, su Windows non è disponibile il metodo AuthenticateAndContinue() della classe WebAuthenticationBroker; oppure, nella classe App su Windows, tra le tipologie di attivazione dell’applicazione non è disponibile il valore ActivationKind.WebAuthenticationBrokerContinuation.

Ecco perciò, ad esempio, come occorre ridefinire il metodo di login per supportare entrambe le piattaforme:

 

privateasyncTask Login()

{

    //Client ID of the Facebook App (retrieved from the Facebook Developers portal)

    var clientId = “your app id”;

    //Required permissions

    var scope = “public_profile, email”;

 

    var redirectUri = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();

    var fb = newFacebookClient();

    var loginUrl = fb.GetLoginUrl(new

    {

        client_id = clientId,

        redirect_uri = redirectUri,

        response_type = “token”,

        scope = scope

    });

 

    Uri startUri = loginUrl;

    Uri endUri = newUri(redirectUri, UriKind.Absolute);

 

 

#if WINDOWS_PHONE_APP

    WebAuthenticationBroker.AuthenticateAndContinue(startUri, endUri, null, WebAuthenticationOptions.None);

#endif

 

#if WINDOWS_APP

WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri, endUri);

await ParseAuthenticationResult(result);

#endif

 

}

In caso la piattaforma corrente sia Windows Phone, viene chiamato il metodo AuthenticateAndContinue() della classe WebAuthenticationBroker; in alternativa, viene chiamato direttamente il metodo AuthenticateAsync() e viene lanciata la procedura di elaborazione dei risultati.

Il progetto di esempio relativo a questo post utilizza questo meccanismo per supportare correttamente sia Windows che Windows Phone.

 

In conclusione

Nel corso di questo post abbiamo visto i passaggi da seguire per integrare Facebook nelle nostre Windows Store App, partendo dalla parte di configurazione (sia sul portale sviluppatori di Windows che su quello di Facebook) per arrivare alla scrittura del codice vera e propria.

Potete scaricare il progetto di esempio da GitHub all’indirizzo https://github.com/qmatteoq/FacebookSample-Universal : ricordati che, per utilizzarlo, dovete sostituire le informazioni di sicurezza (come il client id) con quelle specifiche della vostra applicazione.


Comments (0)

Skip to main content