/* * Authenficateur.java * CodeNameHippie * * Copyright (c) 2015. Philippe Lafontaine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.pam.codenamehippie.http; import android.annotation.SuppressLint; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.util.Log; import com.pam.codenamehippie.HippieApplication; import com.pam.codenamehippie.R; import com.pam.codenamehippie.http.exception.HttpReponseException; import com.pam.codenamehippie.modele.UtilisateurModele; import com.pam.codenamehippie.modele.depot.DepotManager; import com.pam.codenamehippie.modele.depot.UtilisateurModeleDepot; import java.io.IOException; import java.io.Reader; import java.net.HttpRetryException; import okhttp3.Authenticator; import okhttp3.Call; import okhttp3.Challenge; import okhttp3.Credentials; import okhttp3.FormBody; import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.Route; /** * Classe servant de délégué au client HTTP pour les authentification de type Basic. */ public final class Authentificateur implements Authenticator { public interface Callback { void surErreur(IOException e); void surSucces(UtilisateurModele utilisateur); } private static final String TAG = Authentificateur.class.getSimpleName(); private static final HttpUrl CONNECTION_URL = HttpUrl.parse("http://yolainecourteau.com/hippie/laravel/public/"); private final HippieApplication context; private final SharedPreferences preferences; private final String userKey; private final Object lock = new Object(); private volatile String motDePasse; private volatile UtilisateurModele utilisateur; /** * Constructeur de l'authentificateur */ private Authentificateur(HippieApplication context) { this.context = context; this.preferences = PreferenceManager.getDefaultSharedPreferences(this.context); this.userKey = this.context.getString(R.string.pref_user_key); } /** * Méthode usine statique pour créer une nouvelle instance. **/ public static Authentificateur newInstance(HippieApplication context) { return new Authentificateur(context); } public String getMotDePasse() { synchronized (this.lock) { return this.motDePasse; } } public void setMotDePasse(String motDePasse) { synchronized (this.lock) { this.motDePasse = motDePasse; } } public UtilisateurModele getUtilisateur() { synchronized (this.lock) { if (this.utilisateur == null) { String json = this.preferences.getString(this.userKey, null); if (json == null) { return null; } // Charge l'objet utilisateur des pref UtilisateurModeleDepot depot = DepotManager.getInstance().getUtilisateurModeleDepot(); this.utilisateur = depot.fromJson(json); } return this.utilisateur; } } public void setUtilisateur(@NonNull UtilisateurModele utilisateur) { synchronized (this.lock) { this.utilisateur = utilisateur; UtilisateurModeleDepot depot = DepotManager.getInstance().getUtilisateurModeleDepot(); this.preferences.edit().putString(this.userKey, depot.toJson(this.utilisateur)).apply(); } } @Override public Request authenticate(Route route, Response response) throws IOException { Log.d(TAG, "Authenticating for resp: " + response.toString()); for (Challenge challenge : response.challenges()) { Log.d(TAG, "Challenge: " + challenge.toString()); } synchronized (this.lock) { String courriel = this.utilisateur.getCourriel(); if ((courriel != null) && (this.motDePasse != null)) { String credentials = Credentials.basic(courriel, this.motDePasse); return response.request().newBuilder().header("Authorization", credentials).build(); } else { // On lance une exeception si le combo mot de passe/email est pas bon. throw new HttpRetryException(response.message(), response.code(), response.request().url().toString() ); } } } /** * Fonction pour vérifié si on est authentifié. On se considère authentifié si on a un objet * utilisateur ou que l'authentificateur à un mot de passe en mémoire. * * @return Vrai si on est authentifié faux sinon. */ public boolean estAuthentifie() { //TODO: Token d'authenfication return ((this.motDePasse != null) || (this.getUtilisateur() != null)); } /** * Connecte l'application au service web avec l'utilisateur existant. * <p> * Cet méthode est équivalente à: * </p> * <pre class=”prettyprint”> * authenficateur.setMotDePasse(motDePasse); * authenficateur.connecter(callback); * </pre> * Cette méthode est asychrone et retourne immédiatement * * @param motDePasse * mot de passe à utiliser * @param callback * callback pour les résultats de l'opération * */ public void connecter(@NonNull String motDePasse, @NonNull Callback callback) { this.setMotDePasse(motDePasse); RequestBody requestBody = new FormBody.Builder().add("courriel", this.utilisateur.getCourriel()) .add("mot_de_passe", this.getMotDePasse()) .build(); this.connecter(requestBody, callback); } /** * Connecte l'application au service web avec le mot de passe et courriel passé en paramètre. * <p> * Cet méthode est équivalente à: * </p> * <pre class=”prettyprint”> * authenficateur.setMotDePasse(motDePasse); * authenficateur.connecter(callback); * </pre> * Cette méthode est asychrone et retourne immédiatement * * @param motDePasse * mot de passe à utiliser * @param callback * callback pour les résultats de l'opération */ public void connecter(@NonNull String courriel, @NonNull String motDePasse, @NonNull Callback callback) { this.setMotDePasse(motDePasse); RequestBody requestBody = new FormBody.Builder().add("courriel", courriel) .add("mot_de_passe", this.getMotDePasse()) .build(); this.connecter(requestBody, callback); } /** * Connecte l'application au service web. * * @param requestBody trucs à envoyer en paramètres au serveur. * * @param callback * callback pour les résultats de l'opération */ private void connecter(@NonNull RequestBody requestBody, @NonNull final Callback callback) { Request request = new Request.Builder().url(CONNECTION_URL).post(requestBody).build(); this.context.getHttpClient().newCall(request).enqueue(new okhttp3.Callback() { @Override public void onFailure(Call call, final IOException e) { Authentificateur.this.context.runOnUiThread(new Runnable() { @Override public void run() { callback.surErreur(e); } }); // On "déconnecte": on a échoué. Authentificateur.this.deconnecter(); } @Override public void onResponse(Call call, final Response response) throws IOException { if (!response.isSuccessful()) { Authentificateur.this.context.runOnUiThread(new Runnable() { @Override public void run() { callback.surErreur(new HttpReponseException(response)); } }); // On "déconnecte": on a échoué. Authentificateur.this.deconnecter(); } else { UtilisateurModeleDepot depot = DepotManager.getInstance().getUtilisateurModeleDepot(); Reader reader = response.body().charStream(); Authentificateur.this.setUtilisateur(depot.fromJson(reader)); Authentificateur.this.context.runOnUiThread(new Runnable() { @Override public void run() { callback.surSucces(Authentificateur.this.getUtilisateur()); } }); } } }); } @SuppressLint("CommitPrefEdits") public void deconnecter() { // TODO: Mieux gérer l'authentification. this.preferences.edit().remove(this.userKey).commit(); synchronized (this.lock) { this.motDePasse = null; this.utilisateur = null; } } }