// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Use this class to authenticate users with Gaia and access cookies sent
// by the Gaia servers.  This class lives on the SyncEngine_AuthWatcherThread.
//
// Sample usage:
// GaiaAuthenticator gaia_auth("User-Agent", SYNC_SERVICE_NAME,
//     browser_sync::kExternalGaiaUrl);
// if (gaia_auth.Authenticate("email", "passwd", SAVE_IN_MEMORY_ONLY,
//                            true)) { // Synchronous
//   // Do something with: gaia_auth.auth_token(), or gaia_auth.sid(),
//   // or gaia_auth.lsid()
// }
//
// Credentials can also be preserved for subsequent requests, though these are
// saved in plain-text in memory, and not very secure on client systems. The
// email address associated with the Gaia account can be read; the password is
// write-only.

#ifndef CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_
#define CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_

#include <string>

#include "base/basictypes.h"
#include "base/message_loop.h"
#include "chrome/browser/sync/engine/net/http_return.h"
#include "chrome/browser/sync/util/event_sys.h"
#include "chrome/browser/sync/util/signin.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest_prod.h"  // For FRIEND_TEST

namespace browser_sync {

static const char kGaiaUrl[] =
    "https://www.google.com:443/accounts/ClientLogin";

// Use of the following enum is odd. GaiaAuthenticator only looks at
// and DONT_SAVE_CREDENTIALS and SAVE_IN_MEMORY_ONLY (PERSIST_TO_DISK is == to
// SAVE_IN_MEMORY_ONLY for GaiaAuthenticator). The sync engine never uses
// DONT_SAVE_CREDENTIALS. AuthWatcher does look in GaiaAuthenticator's results
// object to decide if it should save credentials to disk. This currently
// works so I'm leaving the odd dance alone.

enum SaveCredentials {
  DONT_SAVE_CREDENTIALS,
  SAVE_IN_MEMORY_ONLY,
  PERSIST_TO_DISK // Saved in both memory and disk
};

// Error codes from Gaia. These will be set correctly for both Gaia V1
// (/ClientAuth) and V2 (/ClientLogin)
enum AuthenticationError {
  None                      = 0,
  BadAuthentication         = 1,
  NotVerified               = 2,
  TermsNotAgreed            = 3,
  Unknown                   = 4,
  AccountDeleted            = 5,
  AccountDisabled           = 6,
  CaptchaRequired           = 7,
  ServiceUnavailable        = 8,
  // Errors generated by this class not Gaia.
  CredentialsNotSet         = 9,
  ConnectionUnavailable     = 10
};

class GaiaAuthenticator;

struct GaiaAuthEvent {
  enum {
    GAIA_AUTH_FAILED,
    GAIA_AUTH_SUCCEEDED,
    GAIA_AUTHENTICATOR_DESTROYED
  }
  what_happened;
  AuthenticationError error;
  const GaiaAuthenticator* authenticator;

  // Lets us use GaiaAuthEvent as its own traits type in hookups.
  typedef GaiaAuthEvent EventType;
  static inline bool IsChannelShutdownEvent(const GaiaAuthEvent& event) {
    return event.what_happened == GAIA_AUTHENTICATOR_DESTROYED;
  }
};

// GaiaAuthenticator can be used to pass user credentials to Gaia and obtain
// cookies set by the Gaia servers.
class GaiaAuthenticator {
  FRIEND_TEST(GaiaAuthenticatorTest, TestNewlineAtEndOfAuthTokenRemoved);
 public:

  // Since GaiaAuthenticator can be used for any service, or by any client, you
  // must include a user-agent and a service-id when creating one. The
  // user_agent is a short string used for simple log analysis. gaia_url is used
  // to choose the server to authenticate with (e.g.
  // http://www.google.com/accounts/ClientLogin).
  GaiaAuthenticator(const std::string& user_agent,
                    const std::string& service_id,
                    const std::string& gaia_url);

  virtual ~GaiaAuthenticator();

  // This object should only be invoked from the AuthWatcherThread message
  // loop, which is injected here.
  void set_message_loop(const MessageLoop* loop) {
    message_loop_ = loop;
  }

  // Pass credentials to authenticate with, or use saved credentials via an
  // overload. If authentication succeeds, you can retrieve the authentication
  // token via the respective accessors. Returns a boolean indicating whether
  // authentication succeeded or not.
  bool Authenticate(const std::string& user_name, const std::string& password,
                    SaveCredentials should_save_credentials,
                    const std::string& captcha_token,
                    const std::string& captcha_value,
                    SignIn try_first);

  bool Authenticate(const std::string& user_name, const std::string& password,
                    SaveCredentials should_save_credentials,
                    SignIn try_first);

  // Pass the LSID to authenticate with. If the authentication succeeds, you can
  // retrieve the authetication token via the respective accessors. Returns a
  // boolean indicating whether authentication succeeded or not.
  bool AuthenticateWithLsid(const std::string& lsid, bool long_lived_token);

  // Resets all stored cookies to their default values.
  void ResetCredentials();

  void SetUsernamePassword(const std::string& username,
                           const std::string& password);

  void SetUsername(const std::string& username);

  void SetAuthToken(const std::string& auth_token, SaveCredentials);

  struct AuthResults {
    SaveCredentials credentials_saved;
    std::string email;
    std::string password;

    // Fields that store various cookies.
    std::string sid;
    std::string lsid;
    std::string auth_token;

    std::string primary_email;

    // Fields for items returned when authentication fails.
    std::string error_msg;
    enum AuthenticationError auth_error;
    std::string auth_error_url;
    std::string captcha_token;
    std::string captcha_url;
    SignIn signin;

    // TODO(skrul): When auth fails, the "signin" field of the results
    // struct never gets set, which causes valgrind to complain.  Give
    // this field a value here so the error is suppressed. It turns
    // out that the signin field has only one possible value, so the
    // correct fix here would be to to remove it entirely.
    AuthResults() : credentials_saved(DONT_SAVE_CREDENTIALS),
                    auth_error(None),
                    signin(GMAIL_SIGNIN) { }
  };

 protected:

  struct AuthParams {
    GaiaAuthenticator* authenticator;
    uint32 request_id;
    SaveCredentials should_save_credentials;
    std::string email;
    std::string password;
    std::string captcha_token;
    std::string captcha_value;
    SignIn try_first;
  };

  // mutex_ must be entered before calling this function.
  AuthParams MakeParams(const std::string& user_name,
                        const std::string& password,
                        SaveCredentials should_save_credentials,
                        const std::string& captcha_token,
                        const std::string& captcha_value,
                        SignIn try_first);

  // The real Authenticate implementations.
  bool AuthenticateImpl(const AuthParams& params);
  bool AuthenticateImpl(const AuthParams& params, AuthResults* results);

  // virtual for testing purposes.
  virtual bool PerformGaiaRequest(const AuthParams& params,
                                  AuthResults* results);
  virtual bool Post(const GURL& url, const std::string& post_body,
                    unsigned long* response_code, std::string* response_body) {
    return false;
  }

  // Caller should fill in results->LSID before calling. Result in
  // results->primary_email.
  virtual bool LookupEmail(AuthResults* results);

 public:
  // Retrieve email.
  inline std::string email() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.email;
  }

  // Retrieve password.
  inline std::string password() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.password;
  }

  // Retrieve AuthToken, if previously authenticated; otherwise returns "".
  inline std::string auth_token() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.auth_token;
  }

  // Retrieve SID cookie. For details, see the Google Accounts documentation.
  inline std::string sid() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.sid;
  }

  // Retrieve LSID cookie. For details, see the Google Accounts documentation.
  inline std::string lsid() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.lsid;
  }

  // Get last authentication error.
  inline enum AuthenticationError auth_error() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.auth_error;
  }

  inline std::string auth_error_url() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.auth_error_url;
  }

  inline std::string captcha_token() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.captcha_token;
  }

  inline std::string captcha_url() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_.captcha_url;
  }

  inline AuthResults results() const {
    DCHECK_EQ(MessageLoop::current(), message_loop_);
    return auth_results_;
  }

  typedef EventChannel<GaiaAuthEvent, Lock> Channel;

  inline Channel* channel() const {
    return channel_;
  }

 private:
  bool IssueAuthToken(AuthResults* results, const std::string& service_id,
                      bool long_lived_token);

  // Helper method to parse response when authentication succeeds.
  void ExtractTokensFrom(const std::string& response, AuthResults* results);
  // Helper method to parse response when authentication fails.
  void ExtractAuthErrorFrom(const std::string& response, AuthResults* results);

  // Fields for the obvious data items.
  const std::string user_agent_;
  const std::string service_id_;
  const std::string gaia_url_;

  AuthResults auth_results_;

  // When multiple async requests are running, only the one that started most
  // recently updates the values.
  //
  // Note that even though this code was written to handle multiple requests
  // simultaneously, the sync code issues auth requests one at a time.
  uint32 request_count_;

  Channel* channel_;

  // Used to compute backoff time for next allowed authentication.
  int delay_;  // In seconds.
  time_t next_allowed_auth_attempt_time_;
  int early_auth_attempt_count_;

  // The message loop all our methods are invoked on.  Generally this is the
  // SyncEngine_AuthWatcherThread's message loop.
  const MessageLoop* message_loop_;
};

}  // namespace browser_sync

#endif  // CHROME_BROWSER_SYNC_ENGINE_NET_GAIA_AUTHENTICATOR_H_