View Javadoc
1   /*
2    * Copyright (C) 2003-2014 eXo Platform SAS.
3    *
4    * This is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU Lesser General Public License as
6    * published by the Free Software Foundation; either version 3 of
7    * the License, or (at your option) any later version.
8    *
9    * This software is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this software; if not, write to the Free
16   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
18   */
19  package org.exoplatform.ui.login;
20  
21  import java.util.ArrayList;
22  
23  import org.exoplatform.R;
24  import org.exoplatform.base.BaseActivity;
25  import org.exoplatform.base.BaseActivity.BasicActivityLifecycleCallbacks;
26  import org.exoplatform.model.ExoAccount;
27  import org.exoplatform.singleton.AccountSetting;
28  import org.exoplatform.singleton.ServerSettingHelper;
29  import org.exoplatform.ui.login.tasks.CheckAccountExistsTask;
30  import org.exoplatform.ui.login.tasks.CheckingTenantStatusTask;
31  import org.exoplatform.ui.login.tasks.LoginTask;
32  import org.exoplatform.ui.login.tasks.RequestTenantTask;
33  import org.exoplatform.utils.CrashUtils;
34  import org.exoplatform.utils.ExoConnectionUtils;
35  import org.exoplatform.utils.ExoConstants;
36  import org.exoplatform.utils.ExoUtils;
37  import org.exoplatform.utils.Log;
38  import org.exoplatform.utils.SettingUtils;
39  import org.exoplatform.widget.WaitingDialog;
40  
41  import android.app.Activity;
42  import android.content.Context;
43  import android.content.res.Resources;
44  import android.os.AsyncTask.Status;
45  import android.os.Bundle;
46  
47  /**
48   * A proxy contains logic relating to network operation. LoginProxy performs
49   * login operation <br/>
50   * 2 possible cases of logging in: <br/>
51   * 1> Login with account configured: <br/>
52   * For this case, this proxy is launched from either LoginActivity or
53   * LaunchActivity <br/>
54   * 2> Login without account configured: <br/>
55   * A procedure is started to determine necessary information such as tenant
56   * name, username ... for the operation <br/>
57   * The proxy, in this case, is launched from either SignInActivity or
58   * SignInOnPremiseActivity: <br/>
59   * - Sign in: log in with email and password <br/>
60   * - On premise: log in with server url, username and password <br/>
61   */
62  public class LoginProxy implements CheckingTenantStatusTask.AsyncTaskListener, RequestTenantTask.AsyncTaskListener,
63      LoginTask.AsyncTaskListener, CheckAccountExistsTask.AsyncTaskListener {
64  
65    // connection status of the app
66    public static boolean                   userIsLoggedIn;
67  
68    /*** === Data === ***/
69    private String                          mNewUserName;
70  
71    private String                          mNewPassword;
72  
73    private String                          mTenant;
74  
75    private String                          mAccountName;
76  
77    private String                          mDomain;
78  
79    private String                          mUpdatedDomain;
80  
81    private String                          mEmail;
82  
83    private Context                         mContext;
84  
85    private AccountSetting                  mSetting;
86  
87    private Resources                       mResource;
88  
89    /** === Async Tasks === **/
90    private LoginTask                       mLoginTask;
91  
92    private RequestTenantTask               mRequestTenantTask;
93  
94    /** the warning dialog that shows error */
95    private LoginWarningDialog              mWarningDialog;
96  
97    private LoginWaitingDialog              mProgressDialog;
98  
99    private BasicActivityLifecycleCallbacks mLifecycleCallback    = new BasicActivityLifecycleCallbacks() {
100 
101     public void onPause(org.exoplatform.base.BaseActivity act) {
102       if (act == mContext) {
103         // TODO implement correct behavior, current only dismissUI and cancel
104         // current task
105         if (Log.LOGD)
106           Log.d(TAG, "onPause cancel task");
107         setListener(null);
108         if (mLoginTask != null && mLoginTask.getStatus() == Status.RUNNING) {
109           mLoginTask.cancel(true);
110         }
111         dismissDialog();
112       }
113     };
114   };
115 
116   /** === States === **/
117   private int                             mLaunchMode;
118 
119   private int                             mState                = WORKING;
120 
121   public static final int                 WITH_EXISTING_ACCOUNT = 0;
122 
123   public static final int                 WITH_USERNAME         = 10;
124 
125   public static final int                 WITH_EMAIL            = 11;
126 
127   public static final int                 SWITCH_ACCOUNT        = 12;
128 
129   public static final String              USERNAME              = "USERNAME";
130 
131   public static final String              PASSWORD              = "PASSWORD";
132 
133   public static final String              EMAIL                 = "EMAIL";
134 
135   public static final String              DOMAIN                = "DOMAIN";
136 
137   public static final String              ACCOUNT_NAME          = "ACCOUNT_NAME";
138 
139   public static final String              SHOW_PROGRESS         = "SHOW_PROGRESS";
140 
141   private static final int                WORKING               = 100;
142 
143   private static final int                FINISHED              = 101;
144 
145   private static final String             TAG                   = "eXo____LoginProxy____";
146 
147   /** data should be verified before entering LoginProxy */
148   public LoginProxy(Context context, int state, Bundle loginData) {
149     SettingUtils.setDefaultLanguage(context);
150     mContext = context;
151     mResource = mContext.getResources();
152     mSetting = AccountSetting.getInstance();
153     mLaunchMode = state;
154     mUpdatedDomain = null;
155     if (mContext instanceof BaseActivity) {
156       ((BaseActivity) mContext).addLifeCycleObserverRef(mLifecycleCallback);
157     }
158     initStates(loginData);
159   }
160 
161   /**
162    * Perform initialization of data <br/>
163    * - if domain supplied, it must start with Http:// <br/>
164    * 
165    * @param loginData
166    */
167   private void initStates(Bundle loginData) {
168 
169     mWarningDialog = new LoginWarningDialog(mContext);
170     mProgressDialog = new LoginWaitingDialog(mContext, null, mResource.getString(R.string.SigningIn));
171 
172     switch (mLaunchMode) {
173 
174     /**
175      * Login from LoginActivity screen If any error happens, - redirect user to
176      * Login screen if not launched from login - otherwise, dismiss the dialog
177      */
178     case WITH_EXISTING_ACCOUNT:
179       mNewUserName = loginData.getString(USERNAME);
180       mNewPassword = loginData.getString(PASSWORD);
181       mDomain = loginData.getString(DOMAIN);
182       mTenant = getTenant(mDomain);
183       // TODO: if this is a cloud server with invalid url, should raise a
184       // warning
185       mEmail = mNewUserName + "@" + mTenant + ".com";
186 
187       mProgressDialog = loginData.getBoolean(SHOW_PROGRESS, true) 
188                                   ? new LoginWaitingDialog(mContext,null,mResource.getString(R.string.SigningIn))
189                                   : null;
190       break;
191 
192     /**
193      * Login from SignInActivity screen
194      */
195     case WITH_EMAIL:
196       mEmail = loginData.getString(EMAIL);
197       mNewPassword = loginData.getString(PASSWORD);
198 
199       if (!checkNetworkConnection())
200         return;
201       mProgressDialog.show();
202 
203       /** figure out which tenant is */
204       mRequestTenantTask = new RequestTenantTask();
205       mRequestTenantTask.setListener(this);
206       mRequestTenantTask.execute(mEmail);
207       break;
208 
209     /**
210      * Login from SignInOnPremiseActivity screen
211      */
212     case WITH_USERNAME:
213       mNewUserName = loginData.getString(USERNAME);
214       mNewPassword = loginData.getString(PASSWORD);
215       mDomain = loginData.getString(DOMAIN);
216       mTenant = getTenant(mDomain);
217       mEmail = mNewUserName + "@" + mTenant + ".com";
218       break;
219     /**
220      * Login from AccountSwitcherFragment screen
221      */
222     case SWITCH_ACCOUNT:
223       mNewUserName = loginData.getString(USERNAME);
224       mNewPassword = loginData.getString(PASSWORD);
225       mDomain = loginData.getString(DOMAIN);
226       mAccountName = loginData.getString(ACCOUNT_NAME);
227       break;
228     }
229 
230   }
231 
232   public LoginWarningDialog getWarningDialog() {
233     return mWarningDialog;
234   }
235 
236   @Override
237   public void onRequestingTenantFinished(int result, String[] userAndTenant) {
238     Log.i(TAG, "onRequestingTenantFinished: " + result);
239     if (result != ExoConnectionUtils.TENANT_OK) {
240       finish(result);
241       return;
242     }
243 
244     mNewUserName = userAndTenant[0];
245     mTenant = userAndTenant[1];
246     mDomain = ExoConnectionUtils.HTTPS + mTenant + "." + ExoConnectionUtils.EXO_CLOUD_WS_DOMAIN;
247 
248     performLogin();
249   }
250 
251   /**
252    * Figure out tenant based on url of server <br/>
253    * <<<<<<< HEAD Url of cloud server must be in the form of<br/>
254    * 
255    * <pre>
256    * http[s]://tenant.exo_cloud_domain
257    * </pre>
258    * 
259    * <br/>
260    * For example: http://exoplatform.exoplatform.net ======= Url of cloud server
261    * must be in the form of http://<tenant>. <exo_cloud_domain> <br/>
262    * For example: http://exoplatform.wks-acc.exoplatform.org >>>>>>> Fix likers
263    * element in activity details screen
264    */
265   private String getTenant(String domain) {
266     // strip off http:// or https://
267     String cloudDomain = domain.startsWith(ExoConnectionUtils.HTTPS) ? domain.substring(ExoConnectionUtils.HTTPS.length())
268                                                                      : domain.startsWith(ExoConnectionUtils.HTTP) ? domain.substring(ExoConnectionUtils.HTTP.length())
269                                                                                                                   : domain;
270     int idx = cloudDomain.indexOf(ExoConnectionUtils.EXO_CLOUD_WS_DOMAIN);
271     if (idx <= 1)
272       return null;
273     String tenant = cloudDomain.substring(0, idx);
274     if (!tenant.endsWith("."))
275       return null; // raise an exception at this point for invalid cloud
276                    // server format
277     tenant = tenant.substring(0, tenant.length() - 1);
278     return (tenant.contains(".")) ? null : tenant;
279   }
280 
281   /**
282    * Actual logic of login
283    */
284   public void performLogin() {
285     if (mState == FINISHED)
286       return;
287 
288     if (!checkNetworkConnection())
289       return;
290 
291     if (mProgressDialog != null && !mProgressDialog.isShowing())
292       mProgressDialog.show();
293 
294     /** cloud server - check tenant status */
295     if (mTenant != null) {
296       CheckingTenantStatusTask checkingTenantStatus = new CheckingTenantStatusTask();
297       checkingTenantStatus.setListener(this);
298       checkingTenantStatus.execute(mTenant, mEmail);
299     } else
300       launchLoginTask();
301   }
302 
303   @Override
304   public void onCheckingTenantStatusFinished(int result) {
305     if (result != ExoConnectionUtils.TENANT_OK) {
306       finish(result);
307       return;
308     }
309 
310     /** cloud server - check email existence */
311     if (mTenant != null && mLaunchMode == WITH_EMAIL) {
312 
313       CheckAccountExistsTask accountExists = new CheckAccountExistsTask();
314       accountExists.setListener(this);
315       accountExists.execute(mNewUserName, mTenant);
316 
317       return;
318 
319     }
320 
321     launchLoginTask();
322 
323   }
324 
325   @Override
326   public void onCheckAccountExistsFinished(boolean accountExists) {
327 
328     if (accountExists)
329       launchLoginTask();
330     else
331       finish(ExoConnectionUtils.SIGNIN_NO_ACCOUNT);
332 
333   }
334 
335   private void launchLoginTask() {
336     mDomain = !(mDomain.startsWith(ExoConnectionUtils.HTTP) || mDomain.startsWith(ExoConnectionUtils.HTTPS))
337         ? ExoConnectionUtils.HTTP + mDomain : mDomain;
338     mLoginTask = new LoginTask();
339     mLoginTask.setListener(this);
340     mLoginTask.execute(mNewUserName, mNewPassword, mDomain);
341   }
342 
343   private boolean checkNetworkConnection() {
344     if (!ExoConnectionUtils.isNetworkAvailableExt(mContext)) {
345       finish(ExoConnectionUtils.SIGNIN_CONNECTION_ERR);
346       return false;
347     }
348     return true;
349   }
350 
351   @Override
352   public void onLoggingInFinished(int result) {
353     try {
354       if (!((Activity) mContext).isFinishing()) {
355         finish(result);
356       } else {
357         String resultStr = result == ExoConnectionUtils.LOGIN_SUCCESS ? "success" : "failed";
358         Log.i(TAG, String.format("Login %s but activity is finishing...", resultStr));
359       }
360     } catch (ClassCastException e) {
361       Log.i(TAG, "Login proxy was not executed in the context of an activity...");
362     }
363 
364   }
365 
366   @Override
367   public void onUpdateDomain(String newDomain) {
368     if (newDomain != null && !newDomain.equalsIgnoreCase(mDomain)) {
369       mUpdatedDomain = newDomain;
370     }
371   }
372 
373   @Override
374   public void onCanceled() {
375     dismissDialog();
376   }
377 
378   private void dismissDialog() {
379     if (mProgressDialog != null && mProgressDialog.isShowing())
380       mProgressDialog.dismiss();
381   }
382 
383   /**
384    * Handling final result of login operation
385    * 
386    * @param result
387    */
388   private void finish(int result) {
389     Log.i(TAG, "PROXY FINISHED - result: " + result);
390 
391     userIsLoggedIn = false;
392 
393     if (mState == FINISHED)
394       return;
395     mState = FINISHED;
396     dismissDialog();
397 
398     switch (result) {
399     case ExoConnectionUtils.LOGIN_INCOMPATIBLE:
400       mWarningDialog.setMessage(mResource.getString(R.string.CompliantMessage)).show();
401       break;
402 
403     case ExoConnectionUtils.SIGNIN_SERVER_NAV:
404       mWarningDialog.setMessage(mResource.getString(R.string.ServerNotAvailable)).show();
405       break;
406 
407     case ExoConnectionUtils.SIGNIN_NO_TENANT_FOR_EMAIL:
408       mWarningDialog.setMessage(mResource.getString(R.string.NoAccountExists)).show();
409       break;
410 
411     case ExoConnectionUtils.SIGNIN_NO_ACCOUNT:
412       mWarningDialog.setMessage(mResource.getString(R.string.NoAccountExists)).show();
413       break;
414 
415     case ExoConnectionUtils.LOGIN_SERVER_RESUMING:
416       mWarningDialog.setMessage(mResource.getString(R.string.ServerResuming)).show();
417       break;
418 
419     case ExoConnectionUtils.LOGIN_UNAUTHORIZED:
420       mWarningDialog.setMessage(mResource.getString(R.string.InvalidCredentials)).show();
421       break;
422 
423     case ExoConnectionUtils.SIGNIN_CONNECTION_ERR:
424       mWarningDialog.setMessage(mResource.getString(R.string.ServerNotAvailable)).show();
425       break;
426 
427     default:
428       mWarningDialog.setMessage(mResource.getString(R.string.ServerNotAvailable)).show();
429       break;
430 
431     /** Login successful - save data */
432     case ExoConnectionUtils.LOGIN_SUCCESS:
433 
434       /* Set social and document settings */
435       StringBuilder builder = new StringBuilder(mDomain).append("_").append(mNewUserName).append("_");
436 
437       mSetting.socialKey = builder.toString() + ExoConstants.SETTING_SOCIAL_FILTER;
438       mSetting.socialKeyIndex = builder.toString() + ExoConstants.SETTING_SOCIAL_FILTER_INDEX;
439       mSetting.documentKey = builder.toString() + ExoConstants.SETTING_DOCUMENT_SHOW_HIDDEN_FILE;
440 
441       ExoAccount newAccountObj;
442       int serverIdx;
443       if (mLaunchMode == SWITCH_ACCOUNT) {
444         newAccountObj = new ExoAccount();
445         newAccountObj.username = mNewUserName;
446         newAccountObj.password = mNewPassword;
447         newAccountObj.serverUrl = mDomain;
448         newAccountObj.accountName = mAccountName;
449       } else if (mLaunchMode == WITH_EXISTING_ACCOUNT) {
450         newAccountObj = mSetting.getCurrentAccount().clone();
451         newAccountObj.username = mNewUserName;
452         newAccountObj.password = mNewPassword;
453       } else {
454         newAccountObj = new ExoAccount();
455         String name = mTenant;
456         if (name == null)
457           name = getTenant(mDomain);
458         if (name == null)
459           name = ExoUtils.getAccountNameFromURL(mDomain, mResource.getString(R.string.DefaultServer));
460         newAccountObj.accountName = ExoUtils.capitalize(name);
461         newAccountObj.serverUrl = mDomain;
462         newAccountObj.username = mNewUserName;
463         newAccountObj.password = mNewPassword;
464       }
465       newAccountObj.lastLoginDate = System.currentTimeMillis();
466 
467       ArrayList<ExoAccount> serverList = ServerSettingHelper.getInstance().getServerInfoList(mContext);
468       int duplicatedIdx = serverList.indexOf(newAccountObj);
469       // The account used to login is not a duplicate, it is added to the
470       // list
471       if (duplicatedIdx == -1) {
472         serverList.add(newAccountObj);
473         serverIdx = serverList.size() - 1;
474       } else {
475         // The account already exists, its index in the list is used as
476         // the current account index
477         ExoAccount duplicatedServer = serverList.get(duplicatedIdx);
478         serverIdx = duplicatedIdx;
479         // The password property is updated if it changed
480         if (!duplicatedServer.password.equals(newAccountObj.password)) {
481           duplicatedServer.password = newAccountObj.password;
482         }
483         // The server's URL is updated if it changed (e.g. from a 301 redir)
484         if (mUpdatedDomain != null) {
485           duplicatedServer.serverUrl = mUpdatedDomain;
486         }
487         duplicatedServer.lastLoginDate = newAccountObj.lastLoginDate;
488       }
489 
490       mSetting.setCurrentAccount(serverList.get(serverIdx));
491       mSetting.setDomainIndex(String.valueOf(serverIdx));
492       userIsLoggedIn = true;
493 
494       // Save config each time to update the last login date property
495       SettingUtils.persistServerSetting(mContext);
496 
497       // Set Crashlytics user information
498       CrashUtils.setUsername(mNewUserName);
499       CrashUtils.setServerInfo(mDomain, ServerSettingHelper.getInstance().getServerVersion());
500       
501       break;
502     }
503 
504     /** invoke listeners */
505     if (mWarningDialog != null && mLaunchMode == SWITCH_ACCOUNT && result != ExoConnectionUtils.LOGIN_SUCCESS) {
506       // when switching account, wait until the user dismisses the dialog
507       // to inform the caller if the login has failed
508       mWarningDialog.setViewListener(new LoginWarningDialog.ViewListener() {
509         @Override
510         public void onClickOk(LoginWarningDialog dialog) {
511           if (mListener != null)
512             mListener.onLoginFinished(false);
513         }
514       });
515     } else if (mListener != null)
516       // otherwise, inform the caller immediately with the result
517       mListener.onLoginFinished(result == ExoConnectionUtils.LOGIN_SUCCESS);
518   }
519 
520   public void onCancelLoad() {
521     if (mLoginTask != null && mLoginTask.getStatus() == LoginTask.Status.RUNNING) {
522       mLoginTask.cancel(true);
523       mLoginTask = null;
524     }
525   }
526 
527   /**
528    * A waiting dialog that only shows once even called multiple times
529    */
530   private class LoginWaitingDialog extends WaitingDialog {
531 
532     public boolean mIsShowing = false;
533 
534     public LoginWaitingDialog(Context context, String titleString, String contentString) {
535       super(context, titleString, contentString);
536     }
537 
538     @Override
539     public void onBackPressed() {
540       super.onBackPressed();
541       onCancelLoad();
542     }
543 
544     @Override
545     public void show() {
546       if (mIsShowing)
547         return;
548       mIsShowing = true;
549       super.show();
550     }
551   }
552 
553   private ProxyListener mListener;
554 
555   public void setListener(ProxyListener listener) {
556     mListener = listener;
557   }
558 
559   public interface ProxyListener {
560     /**
561      * Notify the caller when the proxy has finished
562      * 
563      * @param result true if the login is successful, false otherwise
564      */
565     void onLoginFinished(boolean result);
566   }
567 
568 }