Continuando da discussione su Future Social Authentication Improvements…
Siamo ora nel processo di spostamento di tutte le informazioni relative agli “account associati” in un’unica tabella del database. Questo aiuterà a ridurre significativamente la logica duplicata e consentirà uno sviluppo più rapido in futuro. Ad esempio, la migrazione della nostra logica core di Twitter al nuovo sistema ha ridotto il numero di righe di codice da 136 a sole 24
.
Questo post non è progettato per essere un manuale di istruzioni passo-passo per l’aggiunta di un nuovo provider di autenticazione, ma mira a fornire una panoramica, indicando il codice sorgente rilevante dove necessario.
Implementazione di un autenticatore
Ogni autenticatore deve implementare una sottoclasse di Auth::Authenticator. Per utilizzare la nuova logica condivisa, l’autenticatore può invece estendere Auth::ManagedAuthenticator. Un esempio di implementazione essenziale si può trovare nell’autenticatore Facebook core:
name e register_middleware devono essere sovrascritti dalle classi implementanti, insieme a enable_setting — l’impostazione booleana del sito utilizzata da un amministratore per attivare il provider.
Un autenticatore dovrebbe anche dichiarare required_settings: le impostazioni del sito che devono avere un valore prima che un’autenticazione possa avere successo. La classe di base le utilizza per configured?, e enabled? è enable_setting && configured?, quindi un provider con credenziali mancanti non viene mai pubblicizzato sulla pagina di accesso e la sua rotta /auth/<name> rimane chiusa — altrimenti, facendo clic sul pulsante, l’utente verrebbe lasciato sulla pagina di errore del provider stesso senza modo di tornare indietro. Dichiarare required_settings consente anche ad AuthProviderCredentialsValidator di rifiutare l’abilitazione del provider in primo luogo; collegalo con validator: "AuthProviderCredentialsValidator" sull’impostazione di abilitazione.
def enable_setting
:enable_google_oauth2_logins
end
def required_settings
%i[google_oauth2_client_id google_oauth2_client_secret]
end
Un autenticatore che sovrascrive direttamente enabled? si esenta da entrambi i controlli.
Nota: per la compatibilità multisite, è importante che qualsiasi informazione specifica del sito venga fornita a omniauth in un lambda
setup, anziché essere fissata al momento della definizione. Consulta tutti gli autenticatori core per esempi di questo.
Tutta la logica per collegare gli account esterni agli account Discourse è gestita da Auth::ManagedAuthenticator. Questo si basa sul fatto che il provider omniauth restituisca i dati nel formato definito nella loro documentazione. Se è necessaria qualsiasi manipolazione di questi dati, gli Autenticatori possono sovrascrivere il metodo after_authenticate e manipolare l’auth_token come richiesto. Ad esempio, l’autenticatore Twitter core rimuove tutte le informazioni extra dal token:
I dati sono memorizzati nella tabella del database user_associated_accounts. provider_uid, info, credentials e extra sono tutti presi direttamente dai dati restituiti da omniauth.
Le immagini dei provider vengono scaricate da info.image durante l’autenticazione e conservate nell’avatar_upload_id di ciascun account. Restano separate dall’immagine caricata dall’utente e dai preset selezionabili. Il selettore di avatar offre immagini in cache dai provider abilitati in base alle autorizzazioni esistenti per gli avatar.
UserAvatar gestisce l’importazione, la selezione, l’aggiornamento e la pulizia degli avatar. L’autenticazione programma il recupero del provider tramite UserAvatar.retrieve_for_associated_account; i job di download e gli importatori utilizzano UserAvatar.import_url_for_user, passando associated_account_id per le immagini del provider. I metodi di comodità per l’utente e i callback del ciclo di vita dell’account/caricamento delegano le modifiche dell’avatar a UserAvatar; le autorizzazioni rimangono in Guardian.
user_avatars.selected_user_associated_account_id registra un provider selezionato esplicitamente. I download successivi aggiornano l’avatar visualizzato solo finché quel provider rimane selezionato. I nuovi account selezionano inizialmente il proprio provider se non era stato assegnato un avatar; auth_overrides_avatar continua a far rispettare la selezione del provider. Gli avatar esistenti mantengono la loro apparenza e non viene assegnato loro automaticamente un provider. Gli account collegati esistenti popoleranno le loro scelte di provider al loro prossimo accesso.
La scelta di un altro avatar cancella la selezione del provider. La disconnessione di un account conserva la sua immagine attualmente visualizzata come snapshot locale e conserva qualsiasi immagine caricata separatamente. I download falliti mantengono l’immagine precedente. I download in coda vengono scartati se l’account è stato disconnesso, spostato a un altro utente, o ora fornisce un URL di immagine diverso.
La selezione di Gravatar utilizza ancora il matching dell’upload-ID. Un’immagine personalizzata o preset identica al Gravatar in cache può quindi seguire gli aggiornamenti successivi di Gravatar.
Una volta definita una classe Authenticator, deve essere registrata. Questo deve avvenire precocemente nel ciclo di vita dell’applicazione e non può avvenire all’interno del metodo after_initialize di un plugin. La registrazione minima può semplicemente contenere un riferimento all’autenticatore. In un plugin, la registrazione può essere eseguita utilizzando la funzione auth_provider. Ad esempio:
auth_provider authenticator: OpenIDConnectAuthenticator.new()
Nel core, la registrazione avviene in discourse.rb. Un elenco completo delle opzioni possibili di AuthProvider può essere trovato qui. Il contenuto testuale può essere definito utilizzando queste opzioni, ma è meglio fornire stringhe localizzabili in client.en.yml seguendo le chiavi standard. Ad esempio:
Note aggiuntive su ManagedAuthenticator di @fantasticfears
ManagedAuthenticator in dettaglio
Potresti aver bisogno di lavorare su qualcosa di speciale per l’autenticazione. E vorresti sapere di più su ManagedAuthenticator. Fondamentalmente, ha diverse operazioni, opzioni e controlla come verranno utilizzati i dati.
Discourse gestisce le informazioni degli utenti con due controller. Users::OmniauthCallbacksController gestisce il payload una volta completata l’autenticazione OAuth2. after_authenticate viene chiamato qui. can_connect_existing_user? viene utilizzato anche qui.
Ci sono alcuni metodi privati che puoi leggere per capire come funzionano i diversi campi di dati.
if authenticator.can_connect_existing_user? && current_user
@auth_result = authenticator.after_authenticate(auth, existing_account: current_user)
else
@auth_result = authenticator.after_authenticate(auth)
end
UsersController ha revoke_account che utilizza can_revoke? e revoke. Ma perché il metodo revoke funzioni in remoto, devi costruire la tua implementazione.
UserAuthenticator è una classe di servizio che aiuta ad autenticare (verificando la conferma dell’email o il percorso OAuth2) gli utenti. after_create_account viene chiamato qui.
La logica core rimane in after_authenticate con la classe dati Auth::Result. Seguiamo la struttura dei dati qui. extra_data verrà passato a after_create_account per creare record correlati.
result.extra_data = {
provider: auth_token[:provider],
uid: auth_token[:uid],
info: auth_token[:info],
extra: auth_token[:extra],
credentials: auth_token[:credentials]
}
Proverà a corrispondere e collegarsi a un account esistente.
Potresti chiederti perché la creazione automatica dell’account è possibile ma non c’è User.create. Questo viene fatto in UsersController#create.
authentication = UserAuthenticator.new(user, session)
L’utente è un’istanza nuova che verrà popolata con i dati della sessione preparati dal provider di autenticazione. Credetemi, è solo magia.
Migrazione al nuovo sistema
Per fornire un passaggio senza interruzioni al nuovo sistema, i dati dovrebbero essere migrati dalla vecchia posizione di archiviazione. Per i provider di autenticazione core, queste potrebbero essere tabelle dedicate. Per i plugin, potrebbero essere plugin_store_rows, o oauth2_user_infos. I dati minimi richiesti in una riga di user_associated_accounts sono provider_name, provider_uid e user_id. Per un esempio di migrazione vedere:
Una volta che il sistema ManagedAuthenticator sarà stato rilasciato nel ramo stabile con la v2.2.0, inizieremo a migrare i plugin di autenticazione ufficiali. A questo punto, verrà aggiunto qui un esempio di migrazione di plugin_store_row.
Questo documento è sotto controllo di versione - suggerisci modifiche su github.