summaryrefslogtreecommitdiff
path: root/chromium/google_apis
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/google_apis
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/google_apis')
-rw-r--r--chromium/google_apis/DEPS6
-rw-r--r--chromium/google_apis/OWNERS1
-rwxr-xr-xchromium/google_apis/build/check_internal.py20
-rw-r--r--chromium/google_apis/cup/OWNERS1
-rw-r--r--chromium/google_apis/cup/client_update_protocol.cc305
-rw-r--r--chromium/google_apis/cup/client_update_protocol.h138
-rw-r--r--chromium/google_apis/cup/client_update_protocol_nss.cc81
-rw-r--r--chromium/google_apis/cup/client_update_protocol_openssl.cc27
-rw-r--r--chromium/google_apis/cup/client_update_protocol_unittest.cc165
-rw-r--r--chromium/google_apis/determine_use_official_keys.gypi37
-rw-r--r--chromium/google_apis/gaia/DEPS5
-rw-r--r--chromium/google_apis/gaia/OWNERS3
-rw-r--r--chromium/google_apis/gaia/gaia_auth_consumer.cc51
-rw-r--r--chromium/google_apis/gaia/gaia_auth_consumer.h87
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher.cc949
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher.h391
-rw-r--r--chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc803
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util.cc75
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util.h39
-rw-r--r--chromium/google_apis/gaia/gaia_auth_util_unittest.cc111
-rw-r--r--chromium/google_apis/gaia/gaia_constants.cc65
-rw-r--r--chromium/google_apis/gaia/gaia_constants.h44
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client.cc337
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client.h116
-rw-r--r--chromium/google_apis/gaia/gaia_oauth_client_unittest.cc332
-rw-r--r--chromium/google_apis/gaia/gaia_switches.cc18
-rw-r--r--chromium/google_apis/gaia/gaia_switches.h42
-rw-r--r--chromium/google_apis/gaia/gaia_urls.cc249
-rw-r--r--chromium/google_apis/gaia/gaia_urls.h92
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error.cc272
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error.h213
-rw-r--r--chromium/google_apis/gaia/google_service_auth_error_unittest.cc55
-rw-r--r--chromium/google_apis/gaia/mock_url_fetcher_factory.h70
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_consumer.h31
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher.cc233
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher.h118
-rw-r--r--chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc233
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow.cc172
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow.h126
-rw-r--r--chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc298
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow.cc282
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow.h147
-rw-r--r--chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc364
-rw-r--r--chromium/google_apis/gaia/oauth_request_signer.cc459
-rw-r--r--chromium/google_apis/gaia/oauth_request_signer.h100
-rw-r--r--chromium/google_apis/gaia/oauth_request_signer_unittest.cc323
-rw-r--r--chromium/google_apis/google_api_keys.cc249
-rw-r--r--chromium/google_apis/google_api_keys.h94
-rwxr-xr-xchromium/google_apis/google_api_keys.py90
-rw-r--r--chromium/google_apis/google_api_keys_unittest.cc438
-rw-r--r--chromium/google_apis/google_apis.gyp93
51 files changed, 9050 insertions, 0 deletions
diff --git a/chromium/google_apis/DEPS b/chromium/google_apis/DEPS
new file mode 100644
index 00000000000..367bfb8ac5c
--- /dev/null
+++ b/chromium/google_apis/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "-chrome",
+ "-content",
+ "+crypto",
+ "+net",
+]
diff --git a/chromium/google_apis/OWNERS b/chromium/google_apis/OWNERS
new file mode 100644
index 00000000000..4975363007f
--- /dev/null
+++ b/chromium/google_apis/OWNERS
@@ -0,0 +1 @@
+joi@chromium.org
diff --git a/chromium/google_apis/build/check_internal.py b/chromium/google_apis/build/check_internal.py
new file mode 100755
index 00000000000..da0ddae8052
--- /dev/null
+++ b/chromium/google_apis/build/check_internal.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""google_api's auto-internal gyp integration.
+
+Takes one argument, a path. Prints 1 if the path exists, 0 if not.
+"""
+
+
+import os
+import sys
+
+
+if __name__ == '__main__':
+ if os.path.exists(sys.argv[1]):
+ print 1
+ else:
+ print 0
diff --git a/chromium/google_apis/cup/OWNERS b/chromium/google_apis/cup/OWNERS
new file mode 100644
index 00000000000..00f97260494
--- /dev/null
+++ b/chromium/google_apis/cup/OWNERS
@@ -0,0 +1 @@
+ryanmyers@chromium.org
diff --git a/chromium/google_apis/cup/client_update_protocol.cc b/chromium/google_apis/cup/client_update_protocol.cc
new file mode 100644
index 00000000000..e730557164f
--- /dev/null
+++ b/chromium/google_apis/cup/client_update_protocol.cc
@@ -0,0 +1,305 @@
+// Copyright 2013 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.
+
+#include "google_apis/cup/client_update_protocol.h"
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sha1.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "crypto/hmac.h"
+#include "crypto/random.h"
+
+namespace {
+
+base::StringPiece ByteVectorToSP(const std::vector<uint8>& vec) {
+ if (vec.empty())
+ return base::StringPiece();
+
+ return base::StringPiece(reinterpret_cast<const char*>(&vec[0]), vec.size());
+}
+
+// This class needs to implement the same hashing and signing functions as the
+// Google Update server; for now, this is SHA-1 and HMAC-SHA1, but this may
+// change to SHA-256 in the near future. For this reason, all primitives are
+// wrapped. The name "SymSign" is used to mirror the CUP specification.
+size_t HashDigestSize() {
+ return base::kSHA1Length;
+}
+
+std::vector<uint8> Hash(const std::vector<uint8>& data) {
+ std::vector<uint8> result(HashDigestSize());
+ base::SHA1HashBytes(data.empty() ? NULL : &data[0],
+ data.size(),
+ &result[0]);
+ return result;
+}
+
+std::vector<uint8> Hash(const base::StringPiece& sdata) {
+ std::vector<uint8> result(HashDigestSize());
+ base::SHA1HashBytes(sdata.empty() ?
+ NULL :
+ reinterpret_cast<const unsigned char*>(sdata.data()),
+ sdata.length(),
+ &result[0]);
+ return result;
+}
+
+std::vector<uint8> SymConcat(uint8 id,
+ const std::vector<uint8>* h1,
+ const std::vector<uint8>* h2,
+ const std::vector<uint8>* h3) {
+ std::vector<uint8> result;
+ result.push_back(id);
+ const std::vector<uint8>* args[] = { h1, h2, h3 };
+ for (size_t i = 0; i != arraysize(args); ++i) {
+ if (args[i]) {
+ DCHECK_EQ(args[i]->size(), HashDigestSize());
+ result.insert(result.end(), args[i]->begin(), args[i]->end());
+ }
+ }
+
+ return result;
+}
+
+std::vector<uint8> SymSign(const std::vector<uint8>& key,
+ const std::vector<uint8>& hashes) {
+ DCHECK(!key.empty());
+ DCHECK(!hashes.empty());
+
+ crypto::HMAC hmac(crypto::HMAC::SHA1);
+ if (!hmac.Init(&key[0], key.size()))
+ return std::vector<uint8>();
+
+ std::vector<uint8> result(hmac.DigestLength());
+ if (!hmac.Sign(ByteVectorToSP(hashes), &result[0], result.size()))
+ return std::vector<uint8>();
+
+ return result;
+}
+
+bool SymSignVerify(const std::vector<uint8>& key,
+ const std::vector<uint8>& hashes,
+ const std::vector<uint8>& server_proof) {
+ DCHECK(!key.empty());
+ DCHECK(!hashes.empty());
+ DCHECK(!server_proof.empty());
+
+ crypto::HMAC hmac(crypto::HMAC::SHA1);
+ if (!hmac.Init(&key[0], key.size()))
+ return false;
+
+ return hmac.Verify(ByteVectorToSP(hashes), ByteVectorToSP(server_proof));
+}
+
+// RsaPad() is implemented as described in the CUP spec. It is NOT a general
+// purpose padding algorithm.
+std::vector<uint8> RsaPad(size_t rsa_key_size,
+ const std::vector<uint8>& entropy) {
+ DCHECK_GE(rsa_key_size, HashDigestSize());
+
+ // The result gets padded with zeros if the result size is greater than
+ // the size of the buffer provided by the caller.
+ std::vector<uint8> result(entropy);
+ result.resize(rsa_key_size - HashDigestSize());
+
+ // For use with RSA, the input needs to be smaller than the RSA modulus,
+ // which has always the msb set.
+ result[0] &= 127; // Reset msb
+ result[0] |= 64; // Set second highest bit.
+
+ std::vector<uint8> digest = Hash(result);
+ result.insert(result.end(), digest.begin(), digest.end());
+ DCHECK_EQ(result.size(), rsa_key_size);
+ return result;
+}
+
+// CUP passes the versioned secret in the query portion of the URL for the
+// update check service -- and that means that a URL-safe variant of Base64 is
+// needed. Call the standard Base64 encoder/decoder and then apply fixups.
+std::string UrlSafeB64Encode(const std::vector<uint8>& data) {
+ std::string result;
+ if (!base::Base64Encode(ByteVectorToSP(data), &result))
+ return std::string();
+
+ // Do an tr|+/|-_| on the output, and strip any '=' padding.
+ for (std::string::iterator it = result.begin(); it != result.end(); ++it) {
+ switch (*it) {
+ case '+':
+ *it = '-';
+ break;
+ case '/':
+ *it = '_';
+ break;
+ default:
+ break;
+ }
+ }
+ TrimString(result, "=", &result);
+
+ return result;
+}
+
+std::vector<uint8> UrlSafeB64Decode(const base::StringPiece& input) {
+ std::string unsafe(input.begin(), input.end());
+ for (std::string::iterator it = unsafe.begin(); it != unsafe.end(); ++it) {
+ switch (*it) {
+ case '-':
+ *it = '+';
+ break;
+ case '_':
+ *it = '/';
+ break;
+ default:
+ break;
+ }
+ }
+ if (unsafe.length() % 4)
+ unsafe.append(4 - (unsafe.length() % 4), '=');
+
+ std::string decoded;
+ if (!base::Base64Decode(unsafe, &decoded))
+ return std::vector<uint8>();
+
+ return std::vector<uint8>(decoded.begin(), decoded.end());
+}
+
+} // end namespace
+
+ClientUpdateProtocol::ClientUpdateProtocol(int key_version)
+ : pub_key_version_(key_version) {
+}
+
+scoped_ptr<ClientUpdateProtocol> ClientUpdateProtocol::Create(
+ int key_version,
+ const base::StringPiece& public_key) {
+ DCHECK_GT(key_version, 0);
+ DCHECK(!public_key.empty());
+
+ scoped_ptr<ClientUpdateProtocol> result(
+ new ClientUpdateProtocol(key_version));
+ if (!result)
+ return scoped_ptr<ClientUpdateProtocol>();
+
+ if (!result->LoadPublicKey(public_key))
+ return scoped_ptr<ClientUpdateProtocol>();
+
+ if (!result->BuildRandomSharedKey())
+ return scoped_ptr<ClientUpdateProtocol>();
+
+ return result.Pass();
+}
+
+std::string ClientUpdateProtocol::GetVersionedSecret() const {
+ return base::StringPrintf("%d:%s",
+ pub_key_version_,
+ UrlSafeB64Encode(encrypted_key_source_).c_str());
+}
+
+bool ClientUpdateProtocol::SignRequest(const base::StringPiece& url,
+ const base::StringPiece& request_body,
+ std::string* client_proof) {
+ DCHECK(!encrypted_key_source_.empty());
+ DCHECK(!url.empty());
+ DCHECK(!request_body.empty());
+ DCHECK(client_proof);
+
+ // Compute the challenge hash:
+ // hw = HASH(HASH(v|w)|HASH(request_url)|HASH(body)).
+ // Keep the challenge hash for later to validate the server's response.
+ std::vector<uint8> internal_hashes;
+
+ std::vector<uint8> h;
+ h = Hash(GetVersionedSecret());
+ internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
+ h = Hash(url);
+ internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
+ h = Hash(request_body);
+ internal_hashes.insert(internal_hashes.end(), h.begin(), h.end());
+ DCHECK_EQ(internal_hashes.size(), 3 * HashDigestSize());
+
+ client_challenge_hash_ = Hash(internal_hashes);
+
+ // Sign the challenge hash (hw) using the shared key (sk) to produce the
+ // client proof (cp).
+ std::vector<uint8> raw_client_proof =
+ SymSign(shared_key_, SymConcat(3, &client_challenge_hash_, NULL, NULL));
+ if (raw_client_proof.empty()) {
+ client_challenge_hash_.clear();
+ return false;
+ }
+
+ *client_proof = UrlSafeB64Encode(raw_client_proof);
+ return true;
+}
+
+bool ClientUpdateProtocol::ValidateResponse(
+ const base::StringPiece& response_body,
+ const base::StringPiece& server_cookie,
+ const base::StringPiece& server_proof) {
+ DCHECK(!client_challenge_hash_.empty());
+
+ if (response_body.empty() || server_cookie.empty() || server_proof.empty())
+ return false;
+
+ // Decode the server proof from URL-safe Base64 to a binary HMAC for the
+ // response.
+ std::vector<uint8> sp_decoded = UrlSafeB64Decode(server_proof);
+ if (sp_decoded.empty())
+ return false;
+
+ // If the request was received by the server, the server will use its
+ // private key to decrypt |w_|, yielding the original contents of |r_|.
+ // The server can then recreate |sk_|, compute |hw_|, and SymSign(3|hw)
+ // to ensure that the cp matches the contents. It will then use |sk_|
+ // to sign its response, producing the server proof |sp|.
+ std::vector<uint8> hm = Hash(response_body);
+ std::vector<uint8> hc = Hash(server_cookie);
+ return SymSignVerify(shared_key_,
+ SymConcat(1, &client_challenge_hash_, &hm, &hc),
+ sp_decoded);
+}
+
+bool ClientUpdateProtocol::BuildRandomSharedKey() {
+ DCHECK_GE(PublicKeyLength(), HashDigestSize());
+
+ // Start by generating some random bytes that are suitable to be encrypted;
+ // this will be the source of the shared HMAC key that client and server use.
+ // (CUP specification calls this "r".)
+ std::vector<uint8> key_source;
+ std::vector<uint8> entropy(PublicKeyLength() - HashDigestSize());
+ crypto::RandBytes(&entropy[0], entropy.size());
+ key_source = RsaPad(PublicKeyLength(), entropy);
+
+ return DeriveSharedKey(key_source);
+}
+
+bool ClientUpdateProtocol::SetSharedKeyForTesting(
+ const base::StringPiece& key_source) {
+ DCHECK_EQ(key_source.length(), PublicKeyLength());
+
+ return DeriveSharedKey(std::vector<uint8>(key_source.begin(),
+ key_source.end()));
+}
+
+bool ClientUpdateProtocol::DeriveSharedKey(const std::vector<uint8>& source) {
+ DCHECK(!source.empty());
+ DCHECK_GE(source.size(), HashDigestSize());
+ DCHECK_EQ(source.size(), PublicKeyLength());
+
+ // Hash the key source (r) to generate a new shared HMAC key (sk').
+ shared_key_ = Hash(source);
+
+ // Encrypt the key source (r) using the public key (pk[v]) to generate the
+ // encrypted key source (w).
+ if (!EncryptKeySource(source))
+ return false;
+ if (encrypted_key_source_.size() != PublicKeyLength())
+ return false;
+
+ return true;
+}
+
diff --git a/chromium/google_apis/cup/client_update_protocol.h b/chromium/google_apis/cup/client_update_protocol.h
new file mode 100644
index 00000000000..786c01f42b1
--- /dev/null
+++ b/chromium/google_apis/cup/client_update_protocol.h
@@ -0,0 +1,138 @@
+// Copyright 2013 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.
+
+#ifndef GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_
+#define GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_piece.h"
+
+// Forward declare types for NSS.
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX)
+typedef struct SECKEYPublicKeyStr SECKEYPublicKey;
+#endif
+
+// Client Update Protocol, or CUP, is used by Google Update (Omaha) servers to
+// ensure freshness and authenticity of update checks over HTTP, without the
+// overhead of HTTPS -- namely, no PKI, no guarantee of privacy, and no request
+// replay protection (since update checks are idempotent).
+//
+// http://omaha.googlecode.com/svn/wiki/cup.html
+//
+// Each ClientUpdateProtocol represents a single update check in flight -- a
+// call to SignRequest() generates internal state used by ValidateResponse().
+//
+// This implementation does not persist client proofs by design.
+class ClientUpdateProtocol {
+ public:
+ ~ClientUpdateProtocol();
+
+ // Initializes this instance of CUP with a versioned public key. |key_version|
+ // must be non-negative. |public_key| is expected to be a DER-encoded ASN.1
+ // Subject Public Key Info. Returns a NULL pointer on failure.
+ static scoped_ptr<ClientUpdateProtocol> Create(
+ int key_version, const base::StringPiece& public_key);
+
+ // Returns a versioned encrypted secret (v|w) in a URL-safe Base64 encoding.
+ // Add to your URL before calling SignRequest().
+ std::string GetVersionedSecret() const;
+
+ // Generates freshness/authentication data for an outgoing update check.
+ // |url| contains the the URL that the request will be sent to; it should
+ // include GetVersionedSecret() in its query string. This needs to be
+ // formatted in the way that the Omaha server expects: omit the scheme and
+ // any port number. (e.g. "//tools.google.com/service/update2?w=1:abcdef")
+ // |request_body| contains the body of the update check request in UTF-8.
+ //
+ // On success, returns true, and |client_proof| receives a Base64-encoded
+ // client proof, which should be sent in the If-Match HTTP header. On
+ // failure, returns false, and |client_proof| is not modified.
+ //
+ // This method will store internal state in this instance used by calls to
+ // ValidateResponse(); if you need to have multiple update checks in flight,
+ // initialize a separate CUP instance for each one.
+ bool SignRequest(const base::StringPiece& url,
+ const base::StringPiece& request_body,
+ std::string* client_proof);
+
+ // Validates a response given to a update check request previously signed
+ // with SignRequest(). |request_body| contains the body of the response in
+ // UTF-8. |server_cookie| contains the persisted credential cookie provided
+ // by the server. |server_proof| contains the Base64-encoded server proof,
+ // which is passed in the ETag HTTP header. Returns true if the response is
+ // valid.
+ // This method uses internal state that is set by a prior SignRequest() call.
+ bool ValidateResponse(const base::StringPiece& response_body,
+ const base::StringPiece& server_cookie,
+ const base::StringPiece& server_proof);
+
+ private:
+ friend class CupTest;
+
+ explicit ClientUpdateProtocol(int key_version);
+
+ // Decodes |public_key| into the appropriate internal structures. Returns
+ // the length of the public key (modulus) in bytes, or 0 on failure.
+ bool LoadPublicKey(const base::StringPiece& public_key);
+
+ // Returns the size of the public key in bytes, or 0 on failure.
+ size_t PublicKeyLength();
+
+ // Helper function for BuildSharedKey() -- encrypts |key_source| (r) using
+ // the loaded public key, filling out |encrypted_key_source_| (w).
+ // Returns true on success.
+ bool EncryptKeySource(const std::vector<uint8>& key_source);
+
+ // Generates a random key source and passes it to DeriveSharedKey().
+ // Returns true on success.
+ bool BuildRandomSharedKey();
+
+ // Sets a fixed key source from a character string and passes it to
+ // DeriveSharedKey(). Used for unit testing only. Returns true on success.
+ bool SetSharedKeyForTesting(const base::StringPiece& fixed_key_source);
+
+ // Given a key source (r), derives the values of |shared_key_| (sk') and
+ // encrypted_key_source_ (w). Returns true on success.
+ bool DeriveSharedKey(const std::vector<uint8>& source);
+
+ // The server keeps multiple private keys; a version must be sent so that
+ // the right private key is used to decode the versioned secret. (The CUP
+ // specification calls this "v".)
+ int pub_key_version_;
+
+ // Holds the shared key, which is used to generate an HMAC signature for both
+ // the update check request and the update response. The client builds it
+ // locally, but sends the server an encrypted copy of the key source to
+ // synthesize it on its own. (The CUP specification calls this "sk'".)
+ std::vector<uint8> shared_key_;
+
+ // Holds the original contents of key_source_ that have been encrypted with
+ // the server's public key. The client sends this, along with the version of
+ // the keypair that was used, to the server. The server decrypts it using its
+ // private key to get the contents of key_source_, from which it recreates the
+ // shared key. (The CUP specification calls this "w".)
+ std::vector<uint8> encrypted_key_source_;
+
+ // Holds the hash of the update check request, the URL that it was sent to,
+ // and the versioned secret. This is filled out by a successful call to
+ // SignRequest(), and used by ValidateResponse() to confirm that the server
+ // has successfully decoded the versioned secret and signed the response using
+ // the same shared key as our own. (The CUP specification calls this "hw".)
+ std::vector<uint8> client_challenge_hash_;
+
+ // The public key used to encrypt the key source. (The CUP specification
+ // calls this "pk[v]".)
+#if defined(USE_NSS) || defined(OS_WIN) || defined(OS_MACOSX)
+ SECKEYPublicKey* public_key_;
+#endif
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(ClientUpdateProtocol);
+};
+
+#endif // GOOGLE_APIS_CUP_CLIENT_UPDATE_PROTOCOL_H_
+
diff --git a/chromium/google_apis/cup/client_update_protocol_nss.cc b/chromium/google_apis/cup/client_update_protocol_nss.cc
new file mode 100644
index 00000000000..b369c13d154
--- /dev/null
+++ b/chromium/google_apis/cup/client_update_protocol_nss.cc
@@ -0,0 +1,81 @@
+// Copyright 2013 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.
+
+#include "google_apis/cup/client_update_protocol.h"
+
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <seccomon.h>
+
+#include "base/logging.h"
+#include "crypto/nss_util.h"
+#include "crypto/scoped_nss_types.h"
+
+typedef scoped_ptr_malloc<
+ CERTSubjectPublicKeyInfo,
+ crypto::NSSDestroyer<CERTSubjectPublicKeyInfo,
+ SECKEY_DestroySubjectPublicKeyInfo> >
+ ScopedCERTSubjectPublicKeyInfo;
+
+ClientUpdateProtocol::~ClientUpdateProtocol() {
+ if (public_key_)
+ SECKEY_DestroyPublicKey(public_key_);
+}
+
+bool ClientUpdateProtocol::LoadPublicKey(const base::StringPiece& public_key) {
+ crypto::EnsureNSSInit();
+
+ // The binary blob |public_key| is expected to be a DER-encoded ASN.1
+ // Subject Public Key Info.
+ SECItem spki_item;
+ spki_item.type = siBuffer;
+ spki_item.data =
+ reinterpret_cast<unsigned char*>(const_cast<char*>(public_key.data()));
+ spki_item.len = static_cast<unsigned int>(public_key.size());
+
+ ScopedCERTSubjectPublicKeyInfo spki(
+ SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item));
+ if (!spki.get())
+ return false;
+
+ public_key_ = SECKEY_ExtractPublicKey(spki.get());
+ if (!public_key_)
+ return false;
+
+ if (!PublicKeyLength())
+ return false;
+
+ return true;
+}
+
+size_t ClientUpdateProtocol::PublicKeyLength() {
+ if (!public_key_)
+ return 0;
+
+ return SECKEY_PublicKeyStrength(public_key_);
+}
+
+bool ClientUpdateProtocol::EncryptKeySource(
+ const std::vector<uint8>& key_source) {
+ // WARNING: This call bypasses the usual PKCS #1 padding and does direct RSA
+ // exponentiation. This is not secure without taking measures to ensure that
+ // the contents of r are suitable. This is done to remain compatible with
+ // the implementation on the Google Update servers; don't copy-paste this
+ // code arbitrarily and expect it to work and/or remain secure!
+ if (!public_key_)
+ return false;
+
+ size_t keysize = SECKEY_PublicKeyStrength(public_key_);
+ if (key_source.size() != keysize)
+ return false;
+
+ encrypted_key_source_.resize(keysize);
+ return SECSuccess == PK11_PubEncryptRaw(
+ public_key_,
+ &encrypted_key_source_[0],
+ const_cast<unsigned char*>(&key_source[0]),
+ key_source.size(),
+ NULL);
+}
+
diff --git a/chromium/google_apis/cup/client_update_protocol_openssl.cc b/chromium/google_apis/cup/client_update_protocol_openssl.cc
new file mode 100644
index 00000000000..74f99ec145c
--- /dev/null
+++ b/chromium/google_apis/cup/client_update_protocol_openssl.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 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.
+
+#include "google_apis/cup/client_update_protocol.h"
+
+#include "base/logging.h"
+
+ClientUpdateProtocol::~ClientUpdateProtocol() {}
+
+bool ClientUpdateProtocol::LoadPublicKey(const base::StringPiece& public_key) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+size_t ClientUpdateProtocol::PublicKeyLength() {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+bool ClientUpdateProtocol::EncryptKeySource(
+ const std::vector<uint8>& key_source) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+
diff --git a/chromium/google_apis/cup/client_update_protocol_unittest.cc b/chromium/google_apis/cup/client_update_protocol_unittest.cc
new file mode 100644
index 00000000000..6a2abedce44
--- /dev/null
+++ b/chromium/google_apis/cup/client_update_protocol_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright 2013 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.
+
+#include <limits>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/memory/scoped_ptr.h"
+#include "crypto/random.h"
+#include "crypto/secure_util.h"
+#include "google_apis/cup/client_update_protocol.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+std::string GetPublicKeyForTesting() {
+ // How to generate this key:
+ // openssl genpkey -out cr.pem -outform PEM -algorithm RSA
+ // -pkeyopt rsa_keygen_pubexp:3
+ // openssl rsa -in cr.pem -pubout -out cr_pub.pem
+
+ static const char kCupTestKey1024_Base64[] =
+ "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC7ct1JhLSol2DkBcJdNjR3KkEA"
+ "ZfXpF22lDD2WZu5JAZ4NiZqnHsKGJNPUbCH4AhFsXmuW5wEHhUVNhsMP6F9mQ06D"
+ "i+ygwZ8aXlklmW4S0Et+SNg3i73fnYn0KDQzrzJnMu46s/CFPhjr4f0TH9b7oHkU"
+ "XbqNZtG6gwaN1bmzFwIBAw==";
+
+ std::string result;
+ if (!base::Base64Decode(std::string(kCupTestKey1024_Base64), &result))
+ return "";
+
+ return result;
+}
+
+} // end namespace
+
+#if defined(USE_OPENSSL)
+
+// Once CUP is implemented for OpenSSL, remove this #if block.
+TEST(CupTest, OpenSSLStub) {
+ scoped_ptr<ClientUpdateProtocol> cup =
+ ClientUpdateProtocol::Create(8, GetPublicKeyForTesting());
+ ASSERT_FALSE(cup.get());
+}
+
+#else
+
+class CupTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ cup_ = ClientUpdateProtocol::Create(8, GetPublicKeyForTesting());
+ ASSERT_TRUE(cup_.get());
+ }
+
+ void OverrideRAndRebuildKeys() {
+ // This must be the same length as the modulus of the public key being
+ // used in the unit test.
+ static const size_t kPublicKeyLength = 1024 / 8;
+ static const char kFixedR[] =
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. ";
+
+ ASSERT_EQ(kPublicKeyLength, strlen(kFixedR));
+ ASSERT_TRUE(cup_->SetSharedKeyForTesting(kFixedR));
+ }
+
+ ClientUpdateProtocol& CUP() {
+ return *cup_.get();
+ }
+
+ private:
+ scoped_ptr<ClientUpdateProtocol> cup_;
+};
+
+TEST_F(CupTest, GetVersionedSecret) {
+ // Given a fixed public key set in the test fixture, if the key source |r|
+ // is filled with known data, |w| can be tested against an expected output,
+ // and the signing/verification for an given request becomes fixed.
+ //
+ // The expected output can be generated using this command line, where
+ // plaintext.bin is the contents of kFixedR[] in OverrideRAndRebuildKeys():
+ //
+ // openssl rsautl -inkey cr2_pub.pem -pubin -encrypt -raw
+ // -in plaintext.bin | base64
+ //
+ // Remember to prepend the key version number, and fix up the Base64
+ // afterwards to be URL-safe.
+
+ static const char kExpectedVW[] =
+ "8:lMmNR3mVbOitbq8ceYGStFBwrJcpvY-sauFSbMVe6VONS9x42xTOLY_KdqsWCy"
+ "KuiJBiQziQLOybPUyA9vk0N5kMnC90LIh2nP2FgFG0M0Z22qjB3drsdJPi7TQZbb"
+ "Xhqm587M8vjc6VlM_eoC0qYwCPaXBqXjsyiHnXetcn5X0";
+
+ EXPECT_NE(kExpectedVW, CUP().GetVersionedSecret());
+ OverrideRAndRebuildKeys();
+ EXPECT_EQ(kExpectedVW, CUP().GetVersionedSecret());
+}
+
+TEST_F(CupTest, SignRequest) {
+ static const char kUrl[] = "//testserver.chromium.org/update";
+ static const char kUrlQuery[] = "?junk=present";
+ static const char kRequest[] = "testbody";
+
+ static const char kExpectedCP[] = "tfjmVMDAbU0-Kye4PjrCuyQIDCU";
+
+ OverrideRAndRebuildKeys();
+
+ // Check the case with no query string other than v|w.
+ std::string url(kUrl);
+ url.append("?w=");
+ url.append(CUP().GetVersionedSecret());
+
+ std::string cp;
+ ASSERT_TRUE(CUP().SignRequest(url, kRequest, &cp));
+
+ // Check the case with a pre-existing query string.
+ std::string url2(kUrl);
+ url2.append(kUrlQuery);
+ url2.append("&w=");
+ url2.append(CUP().GetVersionedSecret());
+
+ std::string cp2;
+ ASSERT_TRUE(CUP().SignRequest(url2, kRequest, &cp2));
+
+ // Changes in the URL should result in changes in the client proof.
+ EXPECT_EQ(kExpectedCP, cp2);
+ EXPECT_NE(cp, cp2);
+}
+
+TEST_F(CupTest, ValidateResponse) {
+ static const char kUrl[] = "//testserver.chromium.orgupdate?junk=present&w=";
+ static const char kRequest[] = "testbody";
+
+ static const char kGoodResponse[] = "intact_response";
+ static const char kGoodC[] = "c=EncryptedDataFromTheUpdateServer";
+ static const char kGoodSP[] = "5rMFMPL9Hgqb-2J8kL3scsHeNgg";
+
+ static const char kBadResponse[] = "tampered_response";
+ static const char kBadC[] = "c=TotalJunkThatAnAttackerCouldSend";
+ static const char kBadSP[] = "Base64TamperedShaOneHash";
+
+ OverrideRAndRebuildKeys();
+
+ std::string url(kUrl);
+ url.append(CUP().GetVersionedSecret());
+
+ std::string client_proof;
+ ASSERT_TRUE(CUP().SignRequest(url, kRequest, &client_proof));
+
+ // Return true on a valid response and server proof.
+ EXPECT_TRUE(CUP().ValidateResponse(kGoodResponse, kGoodC, kGoodSP));
+
+ // Return false on anything invalid.
+ EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kGoodC, kGoodSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kBadC, kGoodSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kGoodC, kBadSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kGoodResponse, kBadC, kBadSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kGoodC, kBadSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kBadC, kGoodSP));
+ EXPECT_FALSE(CUP().ValidateResponse(kBadResponse, kBadC, kBadSP));
+}
+
+#endif // !defined(USE_OPENSSL)
+
diff --git a/chromium/google_apis/determine_use_official_keys.gypi b/chromium/google_apis/determine_use_official_keys.gypi
new file mode 100644
index 00000000000..d9f11164af7
--- /dev/null
+++ b/chromium/google_apis/determine_use_official_keys.gypi
@@ -0,0 +1,37 @@
+# Copyright (c) 2013 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.
+
+# Include this .gypi in your target to dynamically set the
+# use_official_google_api_keys variable (unless it is already
+# explicitly set) and the associated preprocessor define.
+
+{
+ 'variables': {
+ 'variables': {
+ # See documentation of this variable in //build/common.gypi.
+ 'use_official_google_api_keys%': 2,
+ },
+
+ # Copy conditionally-set variables out one scope.
+ 'use_official_google_api_keys%': '<(use_official_google_api_keys)',
+
+ 'conditions': [
+ # If use_official_google_api_keys is already set (to 0 or 1), we
+ # do none of the implicit checking. If it is set to 1 and the
+ # internal keys file is missing, the build will fail at compile
+ # time. If it is set to 0 and keys are not provided by other
+ # means, a warning will be printed at compile time.
+ ['use_official_google_api_keys==2', {
+ 'use_official_google_api_keys%':
+ '<!(python <(DEPTH)/google_apis/build/check_internal.py <(DEPTH)/google_apis/internal/google_chrome_api_keys.h)',
+ }],
+ ]
+ },
+
+ 'conditions': [
+ ['use_official_google_api_keys==1', {
+ 'defines': ['USE_OFFICIAL_GOOGLE_API_KEYS=1'],
+ }],
+ ],
+}
diff --git a/chromium/google_apis/gaia/DEPS b/chromium/google_apis/gaia/DEPS
new file mode 100644
index 00000000000..f445dee28bf
--- /dev/null
+++ b/chromium/google_apis/gaia/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+ ".*_[a-z]*test\.cc": [
+ "+content/public/test/test_browser_thread_bundle.h",
+ ]
+}
diff --git a/chromium/google_apis/gaia/OWNERS b/chromium/google_apis/gaia/OWNERS
new file mode 100644
index 00000000000..654a183990f
--- /dev/null
+++ b/chromium/google_apis/gaia/OWNERS
@@ -0,0 +1,3 @@
+rogerta@chromium.org
+tim@chromium.org
+zelidrag@chromium.org
diff --git a/chromium/google_apis/gaia/gaia_auth_consumer.cc b/chromium/google_apis/gaia/gaia_auth_consumer.cc
new file mode 100644
index 00000000000..860b66ec85f
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_consumer.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_auth_consumer.h"
+
+GaiaAuthConsumer::ClientLoginResult::ClientLoginResult()
+ : two_factor(false) {
+}
+
+GaiaAuthConsumer::ClientLoginResult::ClientLoginResult(
+ const std::string& new_sid,
+ const std::string& new_lsid,
+ const std::string& new_token,
+ const std::string& new_data)
+ : sid(new_sid),
+ lsid(new_lsid),
+ token(new_token),
+ data(new_data),
+ two_factor(false) {}
+
+GaiaAuthConsumer::ClientLoginResult::~ClientLoginResult() {}
+
+bool GaiaAuthConsumer::ClientLoginResult::operator==(
+ const ClientLoginResult &b) const {
+ return sid == b.sid &&
+ lsid == b.lsid &&
+ token == b.token &&
+ data == b.data &&
+ two_factor == b.two_factor;
+}
+
+GaiaAuthConsumer::ClientOAuthResult::ClientOAuthResult()
+ : expires_in_secs(0) {}
+
+GaiaAuthConsumer::ClientOAuthResult::ClientOAuthResult(
+ const std::string& new_refresh_token,
+ const std::string& new_access_token,
+ int new_expires_in_secs)
+ : refresh_token(new_refresh_token),
+ access_token(new_access_token),
+ expires_in_secs(new_expires_in_secs) {}
+
+GaiaAuthConsumer::ClientOAuthResult::~ClientOAuthResult() {}
+
+bool GaiaAuthConsumer::ClientOAuthResult::operator==(
+ const ClientOAuthResult &b) const {
+ return refresh_token == b.refresh_token &&
+ access_token == b.access_token &&
+ expires_in_secs == b.expires_in_secs;
+}
diff --git a/chromium/google_apis/gaia/gaia_auth_consumer.h b/chromium/google_apis/gaia/gaia_auth_consumer.h
new file mode 100644
index 00000000000..403033622b2
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_consumer.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_AUTH_CONSUMER_H_
+#define GOOGLE_APIS_GAIA_GAIA_AUTH_CONSUMER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+class GoogleServiceAuthError;
+
+namespace net {
+typedef std::vector<std::string> ResponseCookies;
+}
+
+typedef std::map<std::string, std::string> UserInfoMap;
+
+// An interface that defines the callbacks for objects that
+// GaiaAuthFetcher can return data to.
+class GaiaAuthConsumer {
+ public:
+ struct ClientLoginResult {
+ ClientLoginResult();
+ ClientLoginResult(const std::string& new_sid,
+ const std::string& new_lsid,
+ const std::string& new_token,
+ const std::string& new_data);
+ ~ClientLoginResult();
+
+ bool operator==(const ClientLoginResult &b) const;
+
+ std::string sid;
+ std::string lsid;
+ std::string token;
+ // TODO(chron): Remove the data field later. Don't use it if possible.
+ std::string data; // Full contents of ClientLogin return.
+ bool two_factor; // set to true if there was a TWO_FACTOR "failure".
+ };
+
+ struct ClientOAuthResult {
+ ClientOAuthResult();
+ ClientOAuthResult(const std::string& new_refresh_token,
+ const std::string& new_access_token,
+ int new_expires_in_secs);
+ ~ClientOAuthResult();
+
+ bool operator==(const ClientOAuthResult &b) const;
+
+ // OAuth2 refresh token. Used to mint new access tokens when needed.
+ std::string refresh_token;
+
+ // OAuth2 access token. Token to pass to endpoints that require oauth2
+ // authentication.
+ std::string access_token;
+
+ // The lifespan of |access_token| in seconds.
+ int expires_in_secs;
+ };
+
+ virtual ~GaiaAuthConsumer() {}
+
+ virtual void OnClientLoginSuccess(const ClientLoginResult& result) {}
+ virtual void OnClientLoginFailure(const GoogleServiceAuthError& error) {}
+
+ virtual void OnIssueAuthTokenSuccess(const std::string& service,
+ const std::string& auth_token) {}
+ virtual void OnIssueAuthTokenFailure(const std::string& service,
+ const GoogleServiceAuthError& error) {}
+
+ virtual void OnClientOAuthSuccess(const ClientOAuthResult& result) {}
+ virtual void OnClientOAuthFailure(const GoogleServiceAuthError& error) {}
+
+ virtual void OnOAuth2RevokeTokenCompleted() {}
+
+ virtual void OnGetUserInfoSuccess(const UserInfoMap& data) {}
+ virtual void OnGetUserInfoFailure(const GoogleServiceAuthError& error) {}
+
+ virtual void OnUberAuthTokenSuccess(const std::string& token) {}
+ virtual void OnUberAuthTokenFailure(const GoogleServiceAuthError& error) {}
+
+ virtual void OnMergeSessionSuccess(const std::string& data) {}
+ virtual void OnMergeSessionFailure(const GoogleServiceAuthError& error) {}
+};
+
+#endif // GOOGLE_APIS_GAIA_GAIA_AUTH_CONSUMER_H_
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher.cc b/chromium/google_apis/gaia/gaia_auth_fetcher.cc
new file mode 100644
index 00000000000..67756deb99b
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher.cc
@@ -0,0 +1,949 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+namespace {
+const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES;
+
+static bool CookiePartsContains(const std::vector<std::string>& parts,
+ const char* part) {
+ return std::find(parts.begin(), parts.end(), part) != parts.end();
+}
+
+bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict,
+ std::string* refresh_token,
+ std::string* access_token,
+ int* expires_in_secs) {
+ DCHECK(refresh_token);
+ DCHECK(access_token);
+ DCHECK(expires_in_secs);
+
+ if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) ||
+ !dict->GetStringWithoutPathExpansion("access_token", access_token) ||
+ !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+// TODO(chron): Add sourceless version of this formatter.
+// static
+const char GaiaAuthFetcher::kClientLoginFormat[] =
+ "Email=%s&"
+ "Passwd=%s&"
+ "PersistentCookie=%s&"
+ "accountType=%s&"
+ "source=%s&"
+ "service=%s";
+// static
+const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
+ "Email=%s&"
+ "Passwd=%s&"
+ "PersistentCookie=%s&"
+ "accountType=%s&"
+ "source=%s&"
+ "service=%s&"
+ "logintoken=%s&"
+ "logincaptcha=%s";
+// static
+const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
+ "SID=%s&"
+ "LSID=%s&"
+ "service=%s&"
+ "Session=%s";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
+ "scope=%s&client_id=%s";
+// static
+const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
+ "scope=%s&"
+ "grant_type=authorization_code&"
+ "client_id=%s&"
+ "client_secret=%s&"
+ "code=%s";
+// static
+const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
+ "token=%s";
+// static
+const char GaiaAuthFetcher::kGetUserInfoFormat[] =
+ "LSID=%s";
+// static
+const char GaiaAuthFetcher::kMergeSessionFormat[] =
+ "uberauth=%s&"
+ "continue=%s&"
+ "source=%s";
+// static
+const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
+ "%s?source=%s&"
+ "issueuberauth=1";
+
+const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
+
+// static
+const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
+const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel";
+// static
+const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
+const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis";
+// static
+const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
+const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth";
+// static
+const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
+const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr";
+// static
+const char GaiaAuthFetcher::kServiceUnavailableError[] =
+ "ServiceUnavailable";
+const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] =
+ "ire";
+// static
+const char GaiaAuthFetcher::kErrorParam[] = "Error";
+// static
+const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
+// static
+const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
+// static
+const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
+
+// static
+const char GaiaAuthFetcher::kCookiePersistence[] = "true";
+// static
+// TODO(johnnyg): When hosted accounts are supported by sync,
+// we can always use "HOSTED_OR_GOOGLE"
+const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
+ "HOSTED_OR_GOOGLE";
+const char GaiaAuthFetcher::kAccountTypeGoogle[] =
+ "GOOGLE";
+
+// static
+const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
+
+// static
+const char GaiaAuthFetcher::kAuthHeaderFormat[] =
+ "Authorization: GoogleLogin auth=%s";
+// static
+const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
+// static
+const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
+ "Authorization: Bearer %s";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "Secure";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
+ "HttpOnly";
+// static
+const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
+ "oauth_code=";
+// static
+const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
+ arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
+
+GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
+ const std::string& source,
+ net::URLRequestContextGetter* getter)
+ : consumer_(consumer),
+ getter_(getter),
+ source_(source),
+ client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
+ issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
+ oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
+ oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
+ get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
+ merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
+ uberauth_token_gurl_(base::StringPrintf(kUberAuthTokenURLFormat,
+ GaiaUrls::GetInstance()->oauth1_login_url().c_str(), source.c_str())),
+ oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
+ client_login_to_oauth2_gurl_(
+ GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
+ fetch_pending_(false) {}
+
+GaiaAuthFetcher::~GaiaAuthFetcher() {}
+
+bool GaiaAuthFetcher::HasPendingFetch() {
+ return fetch_pending_;
+}
+
+void GaiaAuthFetcher::CancelRequest() {
+ fetcher_.reset();
+ fetch_pending_ = false;
+}
+
+// static
+net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
+ net::URLRequestContextGetter* getter,
+ const std::string& body,
+ const std::string& headers,
+ const GURL& gaia_gurl,
+ int load_flags,
+ net::URLFetcherDelegate* delegate) {
+ net::URLFetcher* to_return = net::URLFetcher::Create(
+ 0, gaia_gurl,
+ body == "" ? net::URLFetcher::GET : net::URLFetcher::POST,
+ delegate);
+ to_return->SetRequestContext(getter);
+ to_return->SetUploadData("application/x-www-form-urlencoded", body);
+
+ DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
+ DVLOG(2) << "Gaia fetcher headers: " << headers;
+ DVLOG(2) << "Gaia fetcher body: " << body;
+
+ // The Gaia token exchange requests do not require any cookie-based
+ // identification as part of requests. We suppress sending any cookies to
+ // maintain a separation between the user's browsing and Chrome's internal
+ // services. Where such mixing is desired (MergeSession), it will be done
+ // explicitly.
+ to_return->SetLoadFlags(load_flags);
+
+ // Fetchers are sometimes cancelled because a network change was detected,
+ // especially at startup and after sign-in on ChromeOS. Retrying once should
+ // be enough in those cases; let the fetcher retry up to 3 times just in case.
+ // http://crbug.com/163710
+ to_return->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ if (!headers.empty())
+ to_return->SetExtraRequestHeaders(headers);
+
+ return to_return;
+}
+
+// static
+std::string GaiaAuthFetcher::MakeClientLoginBody(
+ const std::string& username,
+ const std::string& password,
+ const std::string& source,
+ const char* service,
+ const std::string& login_token,
+ const std::string& login_captcha,
+ HostedAccountsSetting allow_hosted_accounts) {
+ std::string encoded_username = net::EscapeUrlEncodedData(username, true);
+ std::string encoded_password = net::EscapeUrlEncodedData(password, true);
+ std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
+ true);
+ std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
+ true);
+
+ const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
+ kAccountTypeHostedOrGoogle :
+ kAccountTypeGoogle;
+
+ if (login_token.empty() || login_captcha.empty()) {
+ return base::StringPrintf(kClientLoginFormat,
+ encoded_username.c_str(),
+ encoded_password.c_str(),
+ kCookiePersistence,
+ account_type,
+ source.c_str(),
+ service);
+ }
+
+ return base::StringPrintf(kClientLoginCaptchaFormat,
+ encoded_username.c_str(),
+ encoded_password.c_str(),
+ kCookiePersistence,
+ account_type,
+ source.c_str(),
+ service,
+ encoded_login_token.c_str(),
+ encoded_login_captcha.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
+ const std::string& sid,
+ const std::string& lsid,
+ const char* const service) {
+ std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
+ std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
+
+ // All tokens should be session tokens except the gaia auth token.
+ bool session = true;
+ if (!strcmp(service, GaiaConstants::kGaiaService))
+ session = false;
+
+ return base::StringPrintf(kIssueAuthTokenFormat,
+ encoded_sid.c_str(),
+ encoded_lsid.c_str(),
+ service,
+ session ? "true" : "false");
+}
+
+// static
+std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
+ std::string encoded_scope = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ std::string encoded_client_id = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
+ return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeGetTokenPairBody(
+ const std::string& auth_code) {
+ std::string encoded_scope = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth1_login_scope(), true);
+ std::string encoded_client_id = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
+ std::string encoded_client_secret = net::EscapeUrlEncodedData(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
+ std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
+ return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
+ encoded_scope.c_str(),
+ encoded_client_id.c_str(),
+ encoded_client_secret.c_str(),
+ encoded_auth_code.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeRevokeTokenBody(
+ const std::string& auth_token) {
+ return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
+ std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
+ return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeMergeSessionBody(
+ const std::string& auth_token,
+ const std::string& continue_url,
+ const std::string& source) {
+ std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
+ std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
+ true);
+ std::string encoded_source = net::EscapeUrlEncodedData(source, true);
+ return base::StringPrintf(kMergeSessionFormat,
+ encoded_auth_token.c_str(),
+ encoded_continue_url.c_str(),
+ encoded_source.c_str());
+}
+
+// static
+std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
+ const std::string& auth_token) {
+ return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str());
+}
+
+// Helper method that extracts tokens from a successful reply.
+// static
+void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
+ std::string* sid,
+ std::string* lsid,
+ std::string* token) {
+ using std::vector;
+ using std::pair;
+ using std::string;
+ sid->clear();
+ lsid->clear();
+ token->clear();
+ vector<pair<string, string> > tokens;
+ base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
+ for (vector<pair<string, string> >::iterator i = tokens.begin();
+ i != tokens.end(); ++i) {
+ if (i->first == "SID") {
+ sid->assign(i->second);
+ } else if (i->first == "LSID") {
+ lsid->assign(i->second);
+ } else if (i->first == "Auth") {
+ token->assign(i->second);
+ }
+ }
+ // If this was a request for uberauth token, then that's all we've got in
+ // data.
+ if (sid->empty() && lsid->empty() && token->empty())
+ token->assign(data);
+}
+
+// static
+std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
+ const std::string& source) {
+ std::string encoded_service = net::EscapeUrlEncodedData(service, true);
+ std::string encoded_source = net::EscapeUrlEncodedData(source, true);
+ return base::StringPrintf(kOAuthLoginFormat,
+ encoded_service.c_str(),
+ encoded_source.c_str());
+}
+
+// static
+void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
+ std::string* error,
+ std::string* error_url,
+ std::string* captcha_url,
+ std::string* captcha_token) {
+ using std::vector;
+ using std::pair;
+ using std::string;
+
+ vector<pair<string, string> > tokens;
+ base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
+ for (vector<pair<string, string> >::iterator i = tokens.begin();
+ i != tokens.end(); ++i) {
+ if (i->first == kErrorParam) {
+ error->assign(i->second);
+ } else if (i->first == kErrorUrlParam) {
+ error_url->assign(i->second);
+ } else if (i->first == kCaptchaUrlParam) {
+ captcha_url->assign(i->second);
+ } else if (i->first == kCaptchaTokenParam) {
+ captcha_token->assign(i->second);
+ }
+ }
+}
+
+// static
+bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ const net::ResponseCookies& cookies,
+ std::string* auth_code) {
+ DCHECK(auth_code);
+ net::ResponseCookies::const_iterator iter;
+ for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
+ if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
+ return true;
+ }
+ return false;
+}
+
+// static
+bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
+ std::string* auth_code) {
+ std::vector<std::string> parts;
+ base::SplitString(cookie, ';', &parts);
+ // Per documentation, the cookie should have Secure and HttpOnly.
+ if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
+ !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
+ return false;
+ }
+
+ std::vector<std::string>::const_iterator iter;
+ for (iter = parts.begin(); iter != parts.end(); ++iter) {
+ const std::string& part = *iter;
+ if (StartsWithASCII(
+ part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
+ auth_code->assign(part.substr(
+ kClientLoginToOAuth2CookiePartCodePrefixLength));
+ return true;
+ }
+ }
+ return false;
+}
+
+void GaiaAuthFetcher::StartClientLogin(
+ const std::string& username,
+ const std::string& password,
+ const char* const service,
+ const std::string& login_token,
+ const std::string& login_captcha,
+ HostedAccountsSetting allow_hosted_accounts) {
+
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ // This class is thread agnostic, so be sure to call this only on the
+ // same thread each time.
+ DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
+
+ // Must outlive fetcher_.
+ request_body_ = MakeClientLoginBody(username,
+ password,
+ source_,
+ service,
+ login_token,
+ login_captcha,
+ allow_hosted_accounts);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ client_login_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
+ const std::string& lsid,
+ const char* const service) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting IssueAuthToken for: " << service;
+ requested_service_ = service;
+ request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ issue_auth_token_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
+ const std::string& auth_token) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth login token exchange with auth_token";
+ request_body_ = MakeGetAuthCodeBody();
+ client_login_to_oauth2_gurl_ =
+ GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url());
+
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ MakeGetAuthCodeHeader(auth_token),
+ client_login_to_oauth2_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth2 token revocation";
+ request_body_ = MakeRevokeTokenBody(auth_token);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ oauth2_revoke_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
+ const std::string& session_index) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
+ request_body_ = MakeGetAuthCodeBody();
+
+ std::string url = GaiaUrls::GetInstance()->client_login_to_oauth2_url();
+ if (!session_index.empty())
+ url += "?authuser=" + session_index;
+
+ client_login_to_oauth2_gurl_ = GURL(url);
+
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ client_login_to_oauth2_gurl_,
+ net::LOAD_NORMAL,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
+ const std::string& auth_code) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting OAuth token pair fetch";
+ request_body_ = MakeGetTokenPairBody(auth_code);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ oauth2_token_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
+ request_body_ = MakeGetUserInfoBody(lsid);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ get_user_info_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
+
+ // The continue URL is a required parameter of the MergeSession API, but in
+ // this case we don't actually need or want to navigate to it. Setting it to
+ // an arbitrary Google URL.
+ //
+ // In order for the new session to be merged correctly, the server needs to
+ // know what sessions already exist in the browser. The fetcher needs to be
+ // created such that it sends the cookies with the request, which is
+ // different from all other requests the fetcher can make.
+ std::string continue_url("http://www.google.com");
+ request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_);
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ std::string(),
+ merge_session_gurl_,
+ net::LOAD_NORMAL,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
+ const std::string& access_token) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
+ << access_token;
+ std::string authentication_header =
+ base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ std::string(),
+ authentication_header,
+ uberauth_token_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
+ const std::string& service) {
+ DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
+
+ request_body_ = MakeOAuthLoginBody(service, source_);
+ std::string authentication_header =
+ base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str());
+ fetcher_.reset(CreateGaiaFetcher(getter_,
+ request_body_,
+ authentication_header,
+ oauth_login_gurl_,
+ kLoadFlagsIgnoreCookies,
+ this));
+ fetch_pending_ = true;
+ fetcher_->Start();
+}
+
+// static
+GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
+ const std::string& data,
+ const net::URLRequestStatus& status) {
+ if (!status.is_success()) {
+ if (status.status() == net::URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ } else {
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+ } else {
+ if (IsSecondFactorSuccess(data)) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
+ }
+
+ std::string error;
+ std::string url;
+ std::string captcha_url;
+ std::string captcha_token;
+ ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
+ DLOG(WARNING) << "ClientLogin failed with " << error;
+
+ if (error == kCaptchaError) {
+ GURL image_url(
+ GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url);
+ GURL unlock_url(url);
+ return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
+ captcha_token, image_url, unlock_url);
+ }
+ if (error == kAccountDeletedError)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
+ if (error == kAccountDisabledError)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
+ if (error == kBadAuthenticationError) {
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ }
+ if (error == kServiceUnavailableError) {
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+ }
+
+ DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+ }
+
+ NOTREACHED();
+ return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+}
+
+// static
+GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError(
+ const std::string& data,
+ const net::URLRequestStatus& status) {
+ if (!status.is_success()) {
+ if (status.status() == net::URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ } else {
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+ } else {
+ if (IsSecondFactorSuccess(data)) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
+ }
+
+ std::string error;
+ std::string url;
+ std::string captcha_url;
+ std::string captcha_token;
+ ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
+ LOG(WARNING) << "OAuthLogin failed with " << error;
+
+ if (error == kCaptchaErrorCode) {
+ GURL image_url(
+ GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url);
+ GURL unlock_url(url);
+ return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
+ captcha_token, image_url, unlock_url);
+ }
+ if (error == kAccountDeletedErrorCode)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
+ if (error == kAccountDisabledErrorCode)
+ return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
+ if (error == kBadAuthenticationErrorCode) {
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ }
+ if (error == kServiceUnavailableErrorCode) {
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+ }
+
+ DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
+ return GoogleServiceAuthError(
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+ }
+
+ NOTREACHED();
+ return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+}
+
+void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ DVLOG(1) << "ClientLogin successful!";
+ std::string sid;
+ std::string lsid;
+ std::string token;
+ ParseClientLoginResponse(data, &sid, &lsid, &token);
+ consumer_->OnClientLoginSuccess(
+ GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
+ } else {
+ consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnIssueAuthTokenFetched(
+ const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ // Only the bare token is returned in the body of this Gaia call
+ // without any padding.
+ consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
+ } else {
+ consumer_->OnIssueAuthTokenFailure(requested_service_,
+ GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
+ const std::string& data,
+ const net::ResponseCookies& cookies,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ std::string auth_code;
+ ParseClientLoginToOAuth2Response(cookies, &auth_code);
+ StartAuthCodeForOAuth2TokenExchange(auth_code);
+ } else {
+ consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
+ const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ std::string refresh_token;
+ std::string access_token;
+ int expires_in_secs = 0;
+
+ bool success = false;
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ scoped_ptr<base::Value> value(base::JSONReader::Read(data));
+ if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
+ base::DictionaryValue* dict =
+ static_cast<base::DictionaryValue*>(value.get());
+ success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
+ &access_token, &expires_in_secs);
+ }
+ }
+
+ if (success) {
+ consumer_->OnClientOAuthSuccess(
+ GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
+ expires_in_secs));
+ } else {
+ consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
+ const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ consumer_->OnOAuth2RevokeTokenCompleted();
+}
+
+void GaiaAuthFetcher::OnGetUserInfoFetched(
+ const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ std::vector<std::pair<std::string, std::string> > tokens;
+ UserInfoMap matches;
+ base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
+ std::vector<std::pair<std::string, std::string> >::iterator i;
+ for (i = tokens.begin(); i != tokens.end(); ++i) {
+ matches[i->first] = i->second;
+ }
+ consumer_->OnGetUserInfoSuccess(matches);
+ } else {
+ consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ consumer_->OnMergeSessionSuccess(data);
+ } else {
+ consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ consumer_->OnUberAuthTokenSuccess(data);
+ } else {
+ consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ if (status.is_success() && response_code == net::HTTP_OK) {
+ DVLOG(1) << "ClientLogin successful!";
+ std::string sid;
+ std::string lsid;
+ std::string token;
+ ParseClientLoginResponse(data, &sid, &lsid, &token);
+ consumer_->OnClientLoginSuccess(
+ GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
+ } else {
+ consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
+ }
+}
+
+void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
+ fetch_pending_ = false;
+ // Some of the GAIA requests perform redirects, which results in the final
+ // URL of the fetcher not being the original URL requested. Therefore use
+ // the original URL when determining which OnXXX function to call.
+ const GURL& url = source->GetOriginalURL();
+ const net::URLRequestStatus& status = source->GetStatus();
+ int response_code = source->GetResponseCode();
+ std::string data;
+ source->GetResponseAsString(&data);
+#ifndef NDEBUG
+ std::string headers;
+ if (source->GetResponseHeaders())
+ source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
+ DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
+ << headers << "\n";
+ DVLOG(2) << "data: " << data << "\n";
+#endif
+ // Retrieve the response headers from the request. Must only be called after
+ // the OnURLFetchComplete callback has run.
+ if (url == client_login_gurl_) {
+ OnClientLoginFetched(data, status, response_code);
+ } else if (url == issue_auth_token_gurl_) {
+ OnIssueAuthTokenFetched(data, status, response_code);
+ } else if (url == client_login_to_oauth2_gurl_) {
+ OnClientLoginToOAuth2Fetched(
+ data, source->GetCookies(), status, response_code);
+ } else if (url == oauth2_token_gurl_) {
+ OnOAuth2TokenPairFetched(data, status, response_code);
+ } else if (url == get_user_info_gurl_) {
+ OnGetUserInfoFetched(data, status, response_code);
+ } else if (url == merge_session_gurl_) {
+ OnMergeSessionFetched(data, status, response_code);
+ } else if (url == uberauth_token_gurl_) {
+ OnUberAuthTokenFetch(data, status, response_code);
+ } else if (url == oauth_login_gurl_) {
+ OnOAuthLoginFetched(data, status, response_code);
+ } else if (url == oauth2_revoke_gurl_) {
+ OnOAuth2RevokeTokenFetched(data, status, response_code);
+ } else {
+ NOTREACHED();
+ }
+}
+
+// static
+bool GaiaAuthFetcher::IsSecondFactorSuccess(
+ const std::string& alleged_error) {
+ return alleged_error.find(kSecondFactor) !=
+ std::string::npos;
+}
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher.h b/chromium/google_apis/gaia/gaia_auth_fetcher.h
new file mode 100644
index 00000000000..5021a534ce3
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher.h
@@ -0,0 +1,391 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_AUTH_FETCHER_H_
+#define GOOGLE_APIS_GAIA_GAIA_AUTH_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+// Authenticate a user against the Google Accounts ClientLogin API
+// with various capabilities and return results to a GaiaAuthConsumer.
+//
+// In the future, we will also issue auth tokens from this class.
+// This class should be used on a single thread, but it can be whichever thread
+// that you like.
+//
+// This class can handle one request at a time on any thread. To parallelize
+// requests, create multiple GaiaAuthFetcher's.
+
+class GaiaAuthFetcherTest;
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+class URLRequestStatus;
+}
+
+class GaiaAuthFetcher : public net::URLFetcherDelegate {
+ public:
+ enum HostedAccountsSetting {
+ HostedAccountsAllowed,
+ HostedAccountsNotAllowed
+ };
+
+ // Magic string indicating that, while a second factor is still
+ // needed to complete authentication, the user provided the right password.
+ static const char kSecondFactor[];
+
+ // This will later be hidden behind an auth service which caches
+ // tokens.
+ GaiaAuthFetcher(GaiaAuthConsumer* consumer,
+ const std::string& source,
+ net::URLRequestContextGetter* getter);
+ virtual ~GaiaAuthFetcher();
+
+ // Start a request to obtain the SID and LSID cookies for the the account
+ // identified by |username| and |password|. If |service| is not null or
+ // empty, then also obtains a service token for specified service.
+ //
+ // If this is a second call because of captcha challenge, then the
+ // |login_token| and |login_captcha| arugment should correspond to the
+ // solution of the challenge.
+ //
+ // Either OnClientLoginSuccess or OnClientLoginFailure will be
+ // called on the consumer on the original thread.
+ void StartClientLogin(const std::string& username,
+ const std::string& password,
+ const char* const service,
+ const std::string& login_token,
+ const std::string& login_captcha,
+ HostedAccountsSetting allow_hosted_accounts);
+
+ // Start a request to obtain service token for the the account identified by
+ // |sid| and |lsid| and the |service|.
+ //
+ // Either OnIssueAuthTokenSuccess or OnIssueAuthTokenFailure will be
+ // called on the consumer on the original thread.
+ void StartIssueAuthToken(const std::string& sid,
+ const std::string& lsid,
+ const char* const service);
+
+ // Start a request to obtain |service| token for the the account identified by
+ // |uber_token|.
+ //
+ // Either OnIssueAuthTokenSuccess or OnIssueAuthTokenFailure will be
+ // called on the consumer on the original thread.
+ void StartTokenAuth(const std::string& uber_token,
+ const char* const service);
+
+ // Start a request to obtain service token for the the account identified by
+ // |oauth2_access_token| and the |service|.
+ //
+ // Either OnIssueAuthTokenSuccess or OnIssueAuthTokenFailure will be
+ // called on the consumer on the original thread.
+ void StartIssueAuthTokenForOAuth2(const std::string& oauth2_access_token,
+ const char* const service);
+
+ // Start a request to exchange an "lso" service token given by |auth_token|
+ // for an OAuthLogin-scoped oauth2 token.
+ //
+ // Either OnClientOAuthSuccess or OnClientOAuthFailure will be
+ // called on the consumer on the original thread.
+ void StartLsoForOAuthLoginTokenExchange(const std::string& auth_token);
+
+ // Start a request to revoke |auth_token|.
+ //
+ // OnOAuth2RevokeTokenCompleted will be called on the consumer on the original
+ // thread.
+ void StartRevokeOAuth2Token(const std::string& auth_token);
+
+ // Start a request to exchange the cookies of a signed-in user session
+ // for an OAuthLogin-scoped oauth2 token. In the case of a session with
+ // multiple accounts signed in, |session_index| indicate the which of accounts
+ // within the session.
+ //
+ // Either OnClientOAuthSuccess or OnClientOAuthFailure will be
+ // called on the consumer on the original thread.
+ void StartCookieForOAuthLoginTokenExchange(const std::string& session_index);
+
+ // Start a request to exchange the authorization code for an OAuthLogin-scoped
+ // oauth2 token.
+ //
+ // Either OnClientOAuthSuccess or OnClientOAuthFailure will be
+ // called on the consumer on the original thread.
+ void StartAuthCodeForOAuth2TokenExchange(const std::string& auth_code);
+
+ // Start a request to get user info for the account identified by |lsid|.
+ //
+ // Either OnGetUserInfoSuccess or OnGetUserInfoFailure will be
+ // called on the consumer on the original thread.
+ void StartGetUserInfo(const std::string& lsid);
+
+ // Start a MergeSession request to pre-login the user with the given
+ // credentials.
+ //
+ // Start a MergeSession request to fill the browsing cookie jar with
+ // credentials represented by the account whose uber-auth token is
+ // |uber_token|. This method will modify the cookies of the current profile.
+ //
+ // Either OnMergeSessionSuccess or OnMergeSessionFailure will be
+ // called on the consumer on the original thread.
+ void StartMergeSession(const std::string& uber_token);
+
+ // Start a request to exchange an OAuthLogin-scoped oauth2 access token for an
+ // uber-auth token. The returned token can be used with the method
+ // StartMergeSession().
+ //
+ // Either OnUberAuthTokenSuccess or OnUberAuthTokenFailure will be
+ // called on the consumer on the original thread.
+ void StartTokenFetchForUberAuthExchange(const std::string& access_token);
+
+ // Start a request to exchange an OAuthLogin-scoped oauth2 access token for a
+ // ClientLogin-style service tokens. The response to this request is the
+ // same as the response to a ClientLogin request, except that captcha
+ // challenges are never issued.
+ //
+ // Either OnClientLoginSuccess or OnClientLoginFailure will be
+ // called on the consumer on the original thread. If |service| is empty,
+ // the call will attempt to fetch uber auth token.
+ void StartOAuthLogin(const std::string& access_token,
+ const std::string& service);
+
+ // Implementation of net::URLFetcherDelegate
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ // StartClientLogin been called && results not back yet?
+ bool HasPendingFetch();
+
+ // Stop any URL fetches in progress.
+ void CancelRequest();
+
+ // From a URLFetcher result, generate an appropriate error.
+ // From the API documentation, both IssueAuthToken and ClientLogin have
+ // the same error returns.
+ static GoogleServiceAuthError GenerateOAuthLoginError(
+ const std::string& data,
+ const net::URLRequestStatus& status);
+
+ private:
+ // ClientLogin body constants that don't change
+ static const char kCookiePersistence[];
+ static const char kAccountTypeHostedOrGoogle[];
+ static const char kAccountTypeGoogle[];
+
+ // The format of the POST body for ClientLogin.
+ static const char kClientLoginFormat[];
+ // The format of said POST body when CAPTCHA token & answer are specified.
+ static const char kClientLoginCaptchaFormat[];
+ // The format of the POST body for IssueAuthToken.
+ static const char kIssueAuthTokenFormat[];
+ // The format of the POST body to get OAuth2 auth code from auth token.
+ static const char kClientLoginToOAuth2BodyFormat[];
+ // The format of the POST body to get OAuth2 token pair from auth code.
+ static const char kOAuth2CodeToTokenPairBodyFormat[];
+ // The format of the POST body to revoke an OAuth2 token.
+ static const char kOAuth2RevokeTokenBodyFormat[];
+ // The format of the POST body for GetUserInfo.
+ static const char kGetUserInfoFormat[];
+ // The format of the POST body for MergeSession.
+ static const char kMergeSessionFormat[];
+ // The format of the URL for UberAuthToken.
+ static const char kUberAuthTokenURLFormat[];
+ // The format of the body for OAuthLogin.
+ static const char kOAuthLoginFormat[];
+
+ // Constants for parsing ClientLogin errors.
+ static const char kAccountDeletedError[];
+ static const char kAccountDeletedErrorCode[];
+ static const char kAccountDisabledError[];
+ static const char kAccountDisabledErrorCode[];
+ static const char kBadAuthenticationError[];
+ static const char kBadAuthenticationErrorCode[];
+ static const char kCaptchaError[];
+ static const char kCaptchaErrorCode[];
+ static const char kServiceUnavailableError[];
+ static const char kServiceUnavailableErrorCode[];
+ static const char kErrorParam[];
+ static const char kErrorUrlParam[];
+ static const char kCaptchaUrlParam[];
+ static const char kCaptchaTokenParam[];
+
+ // Constants for parsing ClientOAuth errors.
+ static const char kNeedsAdditional[];
+ static const char kCaptcha[];
+ static const char kTwoFactor[];
+
+ // Constants for request/response for OAuth2 requests.
+ static const char kAuthHeaderFormat[];
+ static const char kOAuthHeaderFormat[];
+ static const char kOAuth2BearerHeaderFormat[];
+ static const char kClientLoginToOAuth2CookiePartSecure[];
+ static const char kClientLoginToOAuth2CookiePartHttpOnly[];
+ static const char kClientLoginToOAuth2CookiePartCodePrefix[];
+ static const int kClientLoginToOAuth2CookiePartCodePrefixLength;
+
+ // Process the results of a ClientLogin fetch.
+ void OnClientLoginFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnIssueAuthTokenFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnClientLoginToOAuth2Fetched(const std::string& data,
+ const net::ResponseCookies& cookies,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnOAuth2TokenPairFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnOAuth2RevokeTokenFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnGetUserInfoFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnMergeSessionFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnUberAuthTokenFetch(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ void OnOAuthLoginFetched(const std::string& data,
+ const net::URLRequestStatus& status,
+ int response_code);
+
+ // Tokenize the results of a ClientLogin fetch.
+ static void ParseClientLoginResponse(const std::string& data,
+ std::string* sid,
+ std::string* lsid,
+ std::string* token);
+
+ static void ParseClientLoginFailure(const std::string& data,
+ std::string* error,
+ std::string* error_url,
+ std::string* captcha_url,
+ std::string* captcha_token);
+
+ // Parse ClientLogin to OAuth2 response.
+ static bool ParseClientLoginToOAuth2Response(
+ const net::ResponseCookies& cookies,
+ std::string* auth_code);
+
+ static bool ParseClientLoginToOAuth2Cookie(const std::string& cookie,
+ std::string* auth_code);
+
+ // Is this a special case Gaia error for TwoFactor auth?
+ static bool IsSecondFactorSuccess(const std::string& alleged_error);
+
+ // Given parameters, create a ClientLogin request body.
+ static std::string MakeClientLoginBody(
+ const std::string& username,
+ const std::string& password,
+ const std::string& source,
+ const char* const service,
+ const std::string& login_token,
+ const std::string& login_captcha,
+ HostedAccountsSetting allow_hosted_accounts);
+ // Supply the sid / lsid returned from ClientLogin in order to
+ // request a long lived auth token for a service.
+ static std::string MakeIssueAuthTokenBody(const std::string& sid,
+ const std::string& lsid,
+ const char* const service);
+ // Create body to get OAuth2 auth code.
+ static std::string MakeGetAuthCodeBody();
+ // Given auth code, create body to get OAuth2 token pair.
+ static std::string MakeGetTokenPairBody(const std::string& auth_code);
+ // Given an OAuth2 token, create body to revoke the token.
+ std::string MakeRevokeTokenBody(const std::string& auth_token);
+ // Supply the lsid returned from ClientLogin in order to fetch
+ // user information.
+ static std::string MakeGetUserInfoBody(const std::string& lsid);
+
+ // Supply the authentication token returned from StartIssueAuthToken.
+ static std::string MakeMergeSessionBody(const std::string& auth_token,
+ const std::string& continue_url,
+ const std::string& source);
+
+ static std::string MakeGetAuthCodeHeader(const std::string& auth_token);
+
+ static std::string MakeOAuthLoginBody(const std::string& service,
+ const std::string& source);
+
+ // Create a fetcher usable for making any Gaia request. |body| is used
+ // as the body of the POST request sent to GAIA. Any strings listed in
+ // |headers| are added as extra HTTP headers in the request.
+ //
+ // |load_flags| are passed to directly to net::URLFetcher::Create() when
+ // creating the URL fetcher.
+ static net::URLFetcher* CreateGaiaFetcher(
+ net::URLRequestContextGetter* getter,
+ const std::string& body,
+ const std::string& headers,
+ const GURL& gaia_gurl,
+ int load_flags,
+ net::URLFetcherDelegate* delegate);
+
+ // From a URLFetcher result, generate an appropriate error.
+ // From the API documentation, both IssueAuthToken and ClientLogin have
+ // the same error returns.
+ static GoogleServiceAuthError GenerateAuthError(
+ const std::string& data,
+ const net::URLRequestStatus& status);
+
+ // These fields are common to GaiaAuthFetcher, same every request
+ GaiaAuthConsumer* const consumer_;
+ net::URLRequestContextGetter* const getter_;
+ std::string source_;
+ const GURL client_login_gurl_;
+ const GURL issue_auth_token_gurl_;
+ const GURL oauth2_token_gurl_;
+ const GURL oauth2_revoke_gurl_;
+ const GURL get_user_info_gurl_;
+ const GURL merge_session_gurl_;
+ const GURL uberauth_token_gurl_;
+ const GURL oauth_login_gurl_;
+
+ // While a fetch is going on:
+ scoped_ptr<net::URLFetcher> fetcher_;
+ GURL client_login_to_oauth2_gurl_;
+ std::string request_body_;
+ std::string requested_service_; // Currently tracked for IssueAuthToken only.
+ bool fetch_pending_;
+
+ friend class GaiaAuthFetcherTest;
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, CaptchaParse);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, AccountDeletedError);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, AccountDisabledError);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, BadAuthenticationError);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, IncomprehensibleError);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ServiceUnavailableError);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, CheckNormalErrorCode);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, CheckTwoFactorResponse);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, LoginNetFailure);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest,
+ ParseClientLoginToOAuth2Response);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ParseOAuth2TokenPairResponse);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ClientOAuthSuccess);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ClientOAuthWithQuote);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ClientOAuthChallengeSuccess);
+ FRIEND_TEST_ALL_PREFIXES(GaiaAuthFetcherTest, ClientOAuthChallengeQuote);
+
+ DISALLOW_COPY_AND_ASSIGN(GaiaAuthFetcher);
+};
+
+#endif // GOOGLE_APIS_GAIA_GAIA_AUTH_FETCHER_H_
diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc
new file mode 100644
index 00000000000..74fed98f47c
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -0,0 +1,803 @@
+// Copyright (c) 2012 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.
+//
+// A complete set of unit tests for GaiaAuthFetcher.
+// Originally ported from GoogleAuthenticator tests.
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/mock_url_fetcher_factory.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+using ::testing::Invoke;
+
+namespace {
+static const char kGetAuthCodeValidCookie[] =
+ "oauth_code=test-code; Path=/test; Secure; HttpOnly";
+static const char kGetAuthCodeCookieNoSecure[] =
+ "oauth_code=test-code; Path=/test; HttpOnly";
+static const char kGetAuthCodeCookieNoHttpOnly[] =
+ "oauth_code=test-code; Path=/test; Secure";
+static const char kGetAuthCodeCookieNoOAuthCode[] =
+ "Path=/test; Secure; HttpOnly";
+static const char kGetTokenPairValidResponse[] =
+ "{"
+ " \"refresh_token\": \"rt1\","
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kClientOAuthValidResponse[] =
+ "{"
+ " \"oauth2\": {"
+ " \"refresh_token\": \"rt1\","
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ " }"
+ "}";
+
+} // namespace
+
+MockFetcher::MockFetcher(bool success,
+ const GURL& url,
+ const std::string& results,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d)
+ : TestURLFetcher(0, url, d) {
+ set_url(url);
+ net::URLRequestStatus::Status code;
+
+ if (success) {
+ set_response_code(net::HTTP_OK);
+ code = net::URLRequestStatus::SUCCESS;
+ } else {
+ set_response_code(net::HTTP_FORBIDDEN);
+ code = net::URLRequestStatus::FAILED;
+ }
+
+ set_status(net::URLRequestStatus(code, 0));
+ SetResponseString(results);
+}
+
+MockFetcher::MockFetcher(const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const net::ResponseCookies& cookies,
+ const std::string& results,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d)
+ : TestURLFetcher(0, url, d) {
+ set_url(url);
+ set_status(status);
+ set_response_code(response_code);
+ set_cookies(cookies);
+ SetResponseString(results);
+}
+
+MockFetcher::~MockFetcher() {}
+
+void MockFetcher::Start() {
+ delegate()->OnURLFetchComplete(this);
+}
+
+class GaiaAuthFetcherTest : public testing::Test {
+ protected:
+ GaiaAuthFetcherTest()
+ : client_login_source_(GaiaUrls::GetInstance()->client_login_url()),
+ issue_auth_token_source_(
+ GaiaUrls::GetInstance()->issue_auth_token_url()),
+ client_login_to_oauth2_source_(
+ GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
+ oauth2_token_source_(GaiaUrls::GetInstance()->oauth2_token_url()),
+ token_auth_source_(GaiaUrls::GetInstance()->token_auth_url()),
+ merge_session_source_(GaiaUrls::GetInstance()->merge_session_url()),
+ uberauth_token_source_(base::StringPrintf(
+ "%s?source=&issueuberauth=1",
+ GaiaUrls::GetInstance()->oauth1_login_url().c_str())),
+ oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()) {}
+
+ void RunParsingTest(const std::string& data,
+ const std::string& sid,
+ const std::string& lsid,
+ const std::string& token) {
+ std::string out_sid;
+ std::string out_lsid;
+ std::string out_token;
+
+ GaiaAuthFetcher::ParseClientLoginResponse(data,
+ &out_sid,
+ &out_lsid,
+ &out_token);
+ EXPECT_EQ(lsid, out_lsid);
+ EXPECT_EQ(sid, out_sid);
+ EXPECT_EQ(token, out_token);
+ }
+
+ void RunErrorParsingTest(const std::string& data,
+ const std::string& error,
+ const std::string& error_url,
+ const std::string& captcha_url,
+ const std::string& captcha_token) {
+ std::string out_error;
+ std::string out_error_url;
+ std::string out_captcha_url;
+ std::string out_captcha_token;
+
+ GaiaAuthFetcher::ParseClientLoginFailure(data,
+ &out_error,
+ &out_error_url,
+ &out_captcha_url,
+ &out_captcha_token);
+ EXPECT_EQ(error, out_error);
+ EXPECT_EQ(error_url, out_error_url);
+ EXPECT_EQ(captcha_url, out_captcha_url);
+ EXPECT_EQ(captcha_token, out_captcha_token);
+ }
+
+ net::ResponseCookies cookies_;
+ GURL client_login_source_;
+ GURL issue_auth_token_source_;
+ GURL client_login_to_oauth2_source_;
+ GURL oauth2_token_source_;
+ GURL token_auth_source_;
+ GURL merge_session_source_;
+ GURL uberauth_token_source_;
+ GURL oauth_login_gurl_;
+
+ protected:
+ net::TestURLRequestContextGetter* GetRequestContext() {
+ if (!request_context_getter_) {
+ request_context_getter_ = new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy());
+ }
+ return request_context_getter_;
+ }
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+};
+
+class MockGaiaConsumer : public GaiaAuthConsumer {
+ public:
+ MockGaiaConsumer() {}
+ ~MockGaiaConsumer() {}
+
+ MOCK_METHOD1(OnClientLoginSuccess, void(const ClientLoginResult& result));
+ MOCK_METHOD2(OnIssueAuthTokenSuccess, void(const std::string& service,
+ const std::string& token));
+ MOCK_METHOD1(OnClientOAuthSuccess,
+ void(const GaiaAuthConsumer::ClientOAuthResult& result));
+ MOCK_METHOD1(OnMergeSessionSuccess, void(const std::string& data));
+ MOCK_METHOD1(OnUberAuthTokenSuccess, void(const std::string& data));
+ MOCK_METHOD1(OnClientLoginFailure,
+ void(const GoogleServiceAuthError& error));
+ MOCK_METHOD2(OnIssueAuthTokenFailure, void(const std::string& service,
+ const GoogleServiceAuthError& error));
+ MOCK_METHOD1(OnClientOAuthFailure,
+ void(const GoogleServiceAuthError& error));
+ MOCK_METHOD1(OnMergeSessionFailure, void(
+ const GoogleServiceAuthError& error));
+ MOCK_METHOD1(OnUberAuthTokenFailure, void(
+ const GoogleServiceAuthError& error));
+};
+
+#if defined(OS_WIN)
+#define MAYBE_ErrorComparator DISABLED_ErrorComparator
+#else
+#define MAYBE_ErrorComparator ErrorComparator
+#endif
+
+TEST_F(GaiaAuthFetcherTest, MAYBE_ErrorComparator) {
+ GoogleServiceAuthError expected_error =
+ GoogleServiceAuthError::FromConnectionError(-101);
+
+ GoogleServiceAuthError matching_error =
+ GoogleServiceAuthError::FromConnectionError(-101);
+
+ EXPECT_TRUE(expected_error == matching_error);
+
+ expected_error = GoogleServiceAuthError::FromConnectionError(6);
+
+ EXPECT_FALSE(expected_error == matching_error);
+
+ expected_error = GoogleServiceAuthError(GoogleServiceAuthError::NONE);
+
+ EXPECT_FALSE(expected_error == matching_error);
+
+ matching_error = GoogleServiceAuthError(GoogleServiceAuthError::NONE);
+
+ EXPECT_TRUE(expected_error == matching_error);
+}
+
+TEST_F(GaiaAuthFetcherTest, LoginNetFailure) {
+ int error_no = net::ERR_CONNECTION_RESET;
+ net::URLRequestStatus status(net::URLRequestStatus::FAILED, error_no);
+
+ GoogleServiceAuthError expected_error =
+ GoogleServiceAuthError::FromConnectionError(error_no);
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginFailure(expected_error))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+
+ MockFetcher mock_fetcher(
+ client_login_source_, status, 0, net::ResponseCookies(), std::string(),
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+TEST_F(GaiaAuthFetcherTest, TokenNetFailure) {
+ int error_no = net::ERR_CONNECTION_RESET;
+ net::URLRequestStatus status(net::URLRequestStatus::FAILED, error_no);
+
+ GoogleServiceAuthError expected_error =
+ GoogleServiceAuthError::FromConnectionError(error_no);
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnIssueAuthTokenFailure(_, expected_error))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+
+ MockFetcher mock_fetcher(
+ issue_auth_token_source_, status, 0, cookies_, std::string(),
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+
+TEST_F(GaiaAuthFetcherTest, LoginDenied) {
+ std::string data("Error=BadAuthentication");
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+
+ GoogleServiceAuthError expected_error(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginFailure(expected_error))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+
+ MockFetcher mock_fetcher(
+ client_login_source_, status, net::HTTP_FORBIDDEN, cookies_, data,
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+TEST_F(GaiaAuthFetcherTest, ParseRequest) {
+ RunParsingTest("SID=sid\nLSID=lsid\nAuth=auth\n", "sid", "lsid", "auth");
+ RunParsingTest("LSID=lsid\nSID=sid\nAuth=auth\n", "sid", "lsid", "auth");
+ RunParsingTest("SID=sid\nLSID=lsid\nAuth=auth", "sid", "lsid", "auth");
+ RunParsingTest("SID=sid\nAuth=auth\n", "sid", std::string(), "auth");
+ RunParsingTest("LSID=lsid\nAuth=auth\n", std::string(), "lsid", "auth");
+ RunParsingTest("\nAuth=auth\n", std::string(), std::string(), "auth");
+ RunParsingTest("SID=sid", "sid", std::string(), std::string());
+}
+
+TEST_F(GaiaAuthFetcherTest, ParseErrorRequest) {
+ RunErrorParsingTest("Url=U\n"
+ "Error=E\n"
+ "CaptchaToken=T\n"
+ "CaptchaUrl=C\n", "E", "U", "C", "T");
+ RunErrorParsingTest("CaptchaToken=T\n"
+ "Error=E\n"
+ "Url=U\n"
+ "CaptchaUrl=C\n", "E", "U", "C", "T");
+ RunErrorParsingTest("\n\n\nCaptchaToken=T\n"
+ "\nError=E\n"
+ "\nUrl=U\n"
+ "CaptchaUrl=C\n", "E", "U", "C", "T");
+}
+
+
+TEST_F(GaiaAuthFetcherTest, OnlineLogin) {
+ std::string data("SID=sid\nLSID=lsid\nAuth=auth\n");
+
+ GaiaAuthConsumer::ClientLoginResult result;
+ result.lsid = "lsid";
+ result.sid = "sid";
+ result.token = "auth";
+ result.data = data;
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginSuccess(result))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ MockFetcher mock_fetcher(
+ client_login_source_, status, net::HTTP_OK, cookies_, data,
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+TEST_F(GaiaAuthFetcherTest, WorkingIssueAuthToken) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnIssueAuthTokenSuccess(_, "token"))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ MockFetcher mock_fetcher(
+ issue_auth_token_source_, status, net::HTTP_OK, cookies_, "token",
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+TEST_F(GaiaAuthFetcherTest, CheckTwoFactorResponse) {
+ std::string response =
+ base::StringPrintf("Error=BadAuthentication\n%s\n",
+ GaiaAuthFetcher::kSecondFactor);
+ EXPECT_TRUE(GaiaAuthFetcher::IsSecondFactorSuccess(response));
+}
+
+TEST_F(GaiaAuthFetcherTest, CheckNormalErrorCode) {
+ std::string response = "Error=BadAuthentication\n";
+ EXPECT_FALSE(GaiaAuthFetcher::IsSecondFactorSuccess(response));
+}
+
+TEST_F(GaiaAuthFetcherTest, TwoFactorLogin) {
+ std::string response = base::StringPrintf("Error=BadAuthentication\n%s\n",
+ GaiaAuthFetcher::kSecondFactor);
+
+ GoogleServiceAuthError error =
+ GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginFailure(error))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ MockFetcher mock_fetcher(
+ client_login_source_, status, net::HTTP_FORBIDDEN, cookies_, response,
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
+
+TEST_F(GaiaAuthFetcherTest, CaptchaParse) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Url=http://www.google.com/login/captcha\n"
+ "Error=CaptchaRequired\n"
+ "CaptchaToken=CCTOKEN\n"
+ "CaptchaUrl=Captcha?ctoken=CCTOKEN\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateAuthError(data, status);
+
+ std::string token = "CCTOKEN";
+ GURL image_url("http://accounts.google.com/Captcha?ctoken=CCTOKEN");
+ GURL unlock_url("http://www.google.com/login/captcha");
+
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::CAPTCHA_REQUIRED);
+ EXPECT_EQ(error.captcha().token, token);
+ EXPECT_EQ(error.captcha().image_url, image_url);
+ EXPECT_EQ(error.captcha().unlock_url, unlock_url);
+}
+
+TEST_F(GaiaAuthFetcherTest, AccountDeletedError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=AccountDeleted\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateAuthError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DELETED);
+}
+
+TEST_F(GaiaAuthFetcherTest, AccountDisabledError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=AccountDisabled\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateAuthError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DISABLED);
+}
+
+TEST_F(GaiaAuthFetcherTest,BadAuthenticationError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=BadAuthentication\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateAuthError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+}
+
+TEST_F(GaiaAuthFetcherTest,IncomprehensibleError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=Gobbledygook\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateAuthError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+}
+
+TEST_F(GaiaAuthFetcherTest,ServiceUnavailableError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=ServiceUnavailable\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthAccountDeletedError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=adel\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DELETED);
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthAccountDisabledError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=adis\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::ACCOUNT_DISABLED);
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthBadAuthenticationError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=badauth\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthServiceUnavailableError) {
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ std::string data = "Error=ire\n";
+ GoogleServiceAuthError error =
+ GaiaAuthFetcher::GenerateOAuthLoginError(data, status);
+ EXPECT_EQ(error.state(), GoogleServiceAuthError::SERVICE_UNAVAILABLE);
+}
+
+TEST_F(GaiaAuthFetcherTest, FullLogin) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginSuccess(_))
+ .Times(1);
+
+ MockURLFetcherFactory<MockFetcher> factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartClientLogin("username",
+ "password",
+ "service",
+ std::string(),
+ std::string(),
+ GaiaAuthFetcher::HostedAccountsAllowed);
+}
+
+TEST_F(GaiaAuthFetcherTest, FullLoginFailure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginFailure(_))
+ .Times(1);
+
+ MockURLFetcherFactory<MockFetcher> factory;
+ factory.set_success(false);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartClientLogin("username",
+ "password",
+ "service",
+ std::string(),
+ std::string(),
+ GaiaAuthFetcher::HostedAccountsAllowed);
+}
+
+TEST_F(GaiaAuthFetcherTest, ClientFetchPending) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginSuccess(_))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartClientLogin("username",
+ "password",
+ "service",
+ std::string(),
+ std::string(),
+ GaiaAuthFetcher::HostedAccountsAllowed);
+
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ client_login_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK, cookies_, "SID=sid\nLSID=lsid\nAuth=auth\n",
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, FullTokenSuccess) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnIssueAuthTokenSuccess("service", "token"))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartIssueAuthToken("sid", "lsid", "service");
+
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ issue_auth_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK, cookies_, "token",
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, FullTokenFailure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnIssueAuthTokenFailure("service", _))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartIssueAuthToken("sid", "lsid", "service");
+
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ issue_auth_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_FORBIDDEN,
+ cookies_,
+ std::string(),
+ net::URLFetcher::GET,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenSuccess) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientOAuthSuccess(
+ GaiaAuthConsumer::ClientOAuthResult("rt1", "at1", 3600))).Times(1);
+
+ net::TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartLsoForOAuthLoginTokenExchange("lso_token");
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ EXPECT_TRUE(NULL != fetcher);
+ EXPECT_EQ(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES,
+ fetcher->GetLoadFlags());
+
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher1(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK,
+ cookies,
+ std::string(),
+ net::URLFetcher::POST,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher1);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher2(
+ oauth2_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK, cookies_, kGetTokenPairValidResponse,
+ net::URLFetcher::POST, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher2);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenWithCookies) {
+ MockGaiaConsumer consumer;
+ net::TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartCookieForOAuthLoginTokenExchange("0");
+ net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
+ EXPECT_TRUE(NULL != fetcher);
+ EXPECT_EQ(net::LOAD_NORMAL, fetcher->GetLoadFlags());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenClientLoginToOAuth2Failure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientOAuthFailure(_))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartLsoForOAuthLoginTokenExchange("lso_token");
+
+ net::ResponseCookies cookies;
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_FORBIDDEN,
+ cookies,
+ std::string(),
+ net::URLFetcher::POST,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, OAuthLoginTokenOAuth2TokenPairFailure) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientOAuthFailure(_))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartLsoForOAuthLoginTokenExchange("lso_token");
+
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher1(
+ client_login_to_oauth2_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK,
+ cookies,
+ std::string(),
+ net::URLFetcher::POST,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher1);
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher2(
+ oauth2_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_FORBIDDEN,
+ cookies_,
+ std::string(),
+ net::URLFetcher::POST,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher2);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, MergeSessionSuccess) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnMergeSessionSuccess("<html></html>"))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartMergeSession("myubertoken");
+
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ merge_session_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK, cookies_, "<html></html>", net::URLFetcher::GET,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, MergeSessionSuccessRedirect) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnMergeSessionSuccess("<html></html>"))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartMergeSession("myubertoken");
+
+ // Make sure the fetcher created has the expected flags. Set its url()
+ // properties to reflect a redirect.
+ net::TestURLFetcher* test_fetcher = factory.GetFetcherByID(0);
+ EXPECT_TRUE(test_fetcher != NULL);
+ EXPECT_TRUE(test_fetcher->GetLoadFlags() == net::LOAD_NORMAL);
+ EXPECT_TRUE(auth.HasPendingFetch());
+
+ GURL final_url("http://www.google.com/CheckCookie");
+ test_fetcher->set_url(final_url);
+ test_fetcher->set_status(
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0));
+ test_fetcher->set_response_code(net::HTTP_OK);
+ test_fetcher->set_cookies(cookies_);
+ test_fetcher->SetResponseString("<html></html>");
+
+ auth.OnURLFetchComplete(test_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, UberAuthTokenSuccess) {
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnUberAuthTokenSuccess("uberToken"))
+ .Times(1);
+
+ net::TestURLFetcherFactory factory;
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ auth.StartTokenFetchForUberAuthExchange("myAccessToken");
+
+ EXPECT_TRUE(auth.HasPendingFetch());
+ MockFetcher mock_fetcher(
+ uberauth_token_source_,
+ net::URLRequestStatus(net::URLRequestStatus::SUCCESS, 0),
+ net::HTTP_OK, cookies_, "uberToken", net::URLFetcher::POST,
+ &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+ EXPECT_FALSE(auth.HasPendingFetch());
+}
+
+TEST_F(GaiaAuthFetcherTest, ParseClientLoginToOAuth2Response) {
+ { // No cookies.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ EXPECT_FALSE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("", auth_code);
+ }
+ { // Few cookies, nothing appropriate.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeCookieNoSecure);
+ cookies.push_back(kGetAuthCodeCookieNoHttpOnly);
+ cookies.push_back(kGetAuthCodeCookieNoOAuthCode);
+ EXPECT_FALSE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("", auth_code);
+ }
+ { // Few cookies, one of them is valid.
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeCookieNoSecure);
+ cookies.push_back(kGetAuthCodeCookieNoHttpOnly);
+ cookies.push_back(kGetAuthCodeCookieNoOAuthCode);
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("test-code", auth_code);
+ }
+ { // Single valid cookie (like in real responses).
+ std::string auth_code;
+ net::ResponseCookies cookies;
+ cookies.push_back(kGetAuthCodeValidCookie);
+ EXPECT_TRUE(GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
+ cookies, &auth_code));
+ EXPECT_EQ("test-code", auth_code);
+ }
+}
+
+TEST_F(GaiaAuthFetcherTest, StartOAuthLogin) {
+ // OAuthLogin returns the same as the ClientLogin endpoint, minus CAPTCHA
+ // responses.
+ std::string data("SID=sid\nLSID=lsid\nAuth=auth\n");
+
+ GaiaAuthConsumer::ClientLoginResult result;
+ result.lsid = "lsid";
+ result.sid = "sid";
+ result.token = "auth";
+ result.data = data;
+
+ MockGaiaConsumer consumer;
+ EXPECT_CALL(consumer, OnClientLoginSuccess(result))
+ .Times(1);
+
+ GaiaAuthFetcher auth(&consumer, std::string(), GetRequestContext());
+ net::URLRequestStatus status(net::URLRequestStatus::SUCCESS, 0);
+ MockFetcher mock_fetcher(
+ oauth_login_gurl_, status, net::HTTP_OK, cookies_, data,
+ net::URLFetcher::GET, &auth);
+ auth.OnURLFetchComplete(&mock_fetcher);
+}
diff --git a/chromium/google_apis/gaia/gaia_auth_util.cc b/chromium/google_apis/gaia/gaia_auth_util.cc
new file mode 100644
index 00000000000..a8cad86408c
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_util.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_auth_util.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "url/gurl.h"
+
+namespace gaia {
+
+namespace {
+const char kGmailDomain[] = "gmail.com";
+}
+
+std::string CanonicalizeEmail(const std::string& email_address) {
+ std::vector<std::string> parts;
+ char at = '@';
+ base::SplitString(email_address, at, &parts);
+ if (parts.size() != 2U)
+ NOTREACHED() << "expecting exactly one @, but got " << parts.size();
+ else if (parts[1] == kGmailDomain) // only strip '.' for gmail accounts.
+ RemoveChars(parts[0], ".", &parts[0]);
+ std::string new_email = StringToLowerASCII(JoinString(parts, at));
+ VLOG(1) << "Canonicalized " << email_address << " to " << new_email;
+ return new_email;
+}
+
+std::string CanonicalizeDomain(const std::string& domain) {
+ // Canonicalization of domain names means lower-casing them. Make sure to
+ // update this function in sync with Canonicalize if this ever changes.
+ return StringToLowerASCII(domain);
+}
+
+std::string SanitizeEmail(const std::string& email_address) {
+ std::string sanitized(email_address);
+
+ // Apply a default domain if necessary.
+ if (sanitized.find('@') == std::string::npos) {
+ sanitized += '@';
+ sanitized += kGmailDomain;
+ }
+
+ return sanitized;
+}
+
+bool AreEmailsSame(const std::string& email1, const std::string& email2) {
+ return gaia::CanonicalizeEmail(gaia::SanitizeEmail(email1)) ==
+ gaia::CanonicalizeEmail(gaia::SanitizeEmail(email2));
+}
+
+std::string ExtractDomainName(const std::string& email_address) {
+ // First canonicalize which will also verify we have proper domain part.
+ std::string email = CanonicalizeEmail(email_address);
+ size_t separator_pos = email.find('@');
+ if (separator_pos != email.npos && separator_pos < email.length() - 1)
+ return email.substr(separator_pos + 1);
+ else
+ NOTREACHED() << "Not a proper email address: " << email;
+ return std::string();
+}
+
+bool IsGaiaSignonRealm(const GURL& url) {
+ if (!url.SchemeIsSecure())
+ return false;
+
+ return url == GaiaUrls::GetInstance()->gaia_url();
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_auth_util.h b/chromium/google_apis/gaia/gaia_auth_util.h
new file mode 100644
index 00000000000..35e834b3629
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_util.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_AUTH_UTIL_H_
+#define GOOGLE_APIS_GAIA_GAIA_AUTH_UTIL_H_
+
+#include <string>
+
+class GURL;
+
+namespace gaia {
+
+// Perform basic canonicalization of |email_address|, taking into account that
+// gmail does not consider '.' or caps inside a username to matter. It also
+// ignores everything after a '+'. For example, c.masone+abc@gmail.com ==
+// cMaSone@gmail.com, per
+// http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313#
+std::string CanonicalizeEmail(const std::string& email_address);
+
+// Returns the canonical form of the given domain.
+std::string CanonicalizeDomain(const std::string& domain);
+
+// Sanitize emails. Currently, it only ensures all emails have a domain by
+// adding gmail.com if no domain is present.
+std::string SanitizeEmail(const std::string& email_address);
+
+// Returns true if the two specified email addresses are the same. Both
+// addresses are first sanitized and then canoncialized before comparing.
+bool AreEmailsSame(const std::string& email1, const std::string& email2);
+
+// Extract the domain part from the canonical form of the given email.
+std::string ExtractDomainName(const std::string& email);
+
+bool IsGaiaSignonRealm(const GURL& url);
+
+} // namespace gaia
+
+#endif // GOOGLE_APIS_GAIA_GAIA_AUTH_UTIL_H_
diff --git a/chromium/google_apis/gaia/gaia_auth_util_unittest.cc b/chromium/google_apis/gaia/gaia_auth_util_unittest.cc
new file mode 100644
index 00000000000..4fe58a3bba4
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_auth_util_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_auth_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace gaia {
+
+TEST(GaiaAuthUtilTest, EmailAddressNoOp) {
+ const char lower_case[] = "user@what.com";
+ EXPECT_EQ(lower_case, CanonicalizeEmail(lower_case));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreCaps) {
+ EXPECT_EQ(CanonicalizeEmail("user@what.com"),
+ CanonicalizeEmail("UsEr@what.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreDomainCaps) {
+ EXPECT_EQ(CanonicalizeEmail("user@what.com"),
+ CanonicalizeEmail("UsEr@what.COM"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressRejectOneUsernameDot) {
+ EXPECT_NE(CanonicalizeEmail("u.ser@what.com"),
+ CanonicalizeEmail("UsEr@what.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressMatchWithOneUsernameDot) {
+ EXPECT_EQ(CanonicalizeEmail("u.ser@what.com"),
+ CanonicalizeEmail("U.sEr@what.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreOneUsernameDot) {
+ EXPECT_EQ(CanonicalizeEmail("us.er@gmail.com"),
+ CanonicalizeEmail("UsEr@gmail.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreManyUsernameDots) {
+ EXPECT_EQ(CanonicalizeEmail("u.ser@gmail.com"),
+ CanonicalizeEmail("Us.E.r@gmail.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreConsecutiveUsernameDots) {
+ EXPECT_EQ(CanonicalizeEmail("use.r@gmail.com"),
+ CanonicalizeEmail("Us....E.r@gmail.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressDifferentOnesRejected) {
+ EXPECT_NE(CanonicalizeEmail("who@what.com"),
+ CanonicalizeEmail("Us....E.r@what.com"));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnorePlusSuffix) {
+ const char with_plus[] = "user+cc@what.com";
+ EXPECT_EQ(with_plus, CanonicalizeEmail(with_plus));
+}
+
+TEST(GaiaAuthUtilTest, EmailAddressIgnoreMultiPlusSuffix) {
+ const char multi_plus[] = "user+cc+bcc@what.com";
+ EXPECT_EQ(multi_plus, CanonicalizeEmail(multi_plus));
+}
+
+TEST(GaiaAuthUtilTest, CanonicalizeDomain) {
+ const char domain[] = "example.com";
+ EXPECT_EQ(domain, CanonicalizeDomain("example.com"));
+ EXPECT_EQ(domain, CanonicalizeDomain("EXAMPLE.cOm"));
+}
+
+TEST(GaiaAuthUtilTest, ExtractDomainName) {
+ const char domain[] = "example.com";
+ EXPECT_EQ(domain, ExtractDomainName("who@example.com"));
+ EXPECT_EQ(domain, ExtractDomainName("who@EXAMPLE.cOm"));
+}
+
+TEST(GaiaAuthUtilTest, SanitizeMissingDomain) {
+ EXPECT_EQ("nodomain@gmail.com", SanitizeEmail("nodomain"));
+}
+
+TEST(GaiaAuthUtilTest, SanitizeExistingDomain) {
+ const char existing[] = "test@example.com";
+ EXPECT_EQ(existing, SanitizeEmail(existing));
+}
+
+TEST(GaiaAuthUtilTest, AreEmailsSame) {
+ EXPECT_TRUE(AreEmailsSame("foo", "foo"));
+ EXPECT_TRUE(AreEmailsSame("foo", "foo@gmail.com"));
+ EXPECT_TRUE(AreEmailsSame("foo@gmail.com", "Foo@Gmail.com"));
+ EXPECT_FALSE(AreEmailsSame("foo@gmail.com", "foo@othermail.com"));
+ EXPECT_FALSE(AreEmailsSame("user@gmail.com", "foo@gmail.com"));
+}
+
+TEST(GaiaAuthUtilTest, IsGaiaSignonRealm) {
+ // Only https versions of Gaia URLs should be considered valid.
+ EXPECT_TRUE(IsGaiaSignonRealm(GURL("https://accounts.google.com/")));
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("http://accounts.google.com/")));
+
+ // Other Google URLs are not valid.
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("https://www.google.com/")));
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("http://www.google.com/")));
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("https://google.com/")));
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("https://mail.google.com/")));
+
+ // Other https URLs are not valid.
+ EXPECT_FALSE(IsGaiaSignonRealm(GURL("https://www.example.com/")));
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_constants.cc b/chromium/google_apis/gaia/gaia_constants.cc
new file mode 100644
index 00000000000..d1d932fb037
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_constants.cc
@@ -0,0 +1,65 @@
+// Copyright (c) 2012 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.
+//
+// Constants definitions
+
+#include "google_apis/gaia/gaia_constants.h"
+
+namespace GaiaConstants {
+
+// Gaia uses this for accounting where login is coming from.
+const char kChromeOSSource[] = "chromeos";
+const char kChromeSource[] = "ChromiumBrowser";
+
+// Service name for Gaia. Used to convert to cookie auth.
+const char kGaiaService[] = "gaia";
+// Service name for Picasa API. API is used to get user's image.
+const char kPicasaService[] = "lh2";
+
+// Service/scope names for sync.
+const char kSyncService[] = "chromiumsync";
+
+// Service name for remoting.
+const char kRemotingService[] = "chromoting";
+
+// Service/scope names for device management (cloud-based policy) server.
+const char kDeviceManagementServiceOAuth[] =
+ "https://www.googleapis.com/auth/chromeosdevicemanagement";
+
+// OAuth2 scope for access to all Google APIs.
+const char kAnyApiOAuth2Scope[] = "https://www.googleapis.com/auth/any-api";
+
+// OAuth2 scope for access to Chrome sync APIs
+const char kChromeSyncOAuth2Scope[] =
+ "https://www.googleapis.com/auth/chromesync";
+// OAuth2 scope for access to the Chrome Sync APIs for managed profiles.
+const char kChromeSyncManagedOAuth2Scope[] =
+ "https://www.googleapis.com/auth/chromesync_playpen";
+// OAuth2 scope for access to Google Talk APIs (XMPP).
+const char kGoogleTalkOAuth2Scope[] =
+ "https://www.googleapis.com/auth/googletalk";
+
+// Service for LSO endpoint of Google that exposes OAuth APIs.
+const char kLSOService[] = "lso";
+
+// Used to mint uber auth tokens when needed.
+const char kGaiaSid[] = "sid";
+const char kGaiaLsid[] = "lsid";
+const char kGaiaOAuthToken[] = "oauthToken";
+const char kGaiaOAuthSecret[] = "oauthSecret";
+const char kGaiaOAuthDuration[] = "3600";
+const char kGaiaOAuth2LoginRefreshToken[] = "oauth2LoginRefreshToken";
+
+// Used to construct a channel ID for push messaging.
+const char kObfuscatedGaiaId[] = "obfuscatedGaiaId";
+
+// Used to build ClientOAuth requests. These are the names of keys used when
+// building base::DictionaryValue that represent the json data that makes up
+// the ClientOAuth endpoint protocol. The comment above each constant explains
+// what value is associated with that key.
+
+// Canonical email of the account to sign in.
+const char kClientOAuthEmailKey[] = "email";
+
+} // namespace GaiaConstants
diff --git a/chromium/google_apis/gaia/gaia_constants.h b/chromium/google_apis/gaia/gaia_constants.h
new file mode 100644
index 00000000000..72cae741b9b
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_constants.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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.
+//
+// Constants used by IssueAuthToken and ClientLogin
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
+#define GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
+
+namespace GaiaConstants {
+
+// Gaia sources for accounting
+extern const char kChromeOSSource[];
+extern const char kChromeSource[];
+
+// Gaia services for requesting
+extern const char kGaiaService[]; // uber token
+extern const char kPicasaService[];
+extern const char kSyncService[];
+extern const char kRemotingService[];
+extern const char kDeviceManagementServiceOAuth[];
+extern const char kAnyApiOAuth2Scope[];
+extern const char kChromeSyncOAuth2Scope[];
+extern const char kChromeSyncManagedOAuth2Scope[];
+extern const char kGoogleTalkOAuth2Scope[];
+extern const char kLSOService[];
+
+// Used with uber auth tokens when needed.
+extern const char kGaiaSid[];
+extern const char kGaiaLsid[];
+extern const char kGaiaOAuthToken[];
+extern const char kGaiaOAuthSecret[];
+extern const char kGaiaOAuthDuration[];
+extern const char kGaiaOAuth2LoginRefreshToken[];
+
+// Used to construct a channel ID for push messaging.
+extern const char kObfuscatedGaiaId[];
+
+// Used by wallet sign in helper.
+extern const char kClientOAuthEmailKey[];
+
+} // namespace GaiaConstants
+
+#endif // GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
diff --git a/chromium/google_apis/gaia/gaia_oauth_client.cc b/chromium/google_apis/gaia/gaia_oauth_client.cc
new file mode 100644
index 00000000000..86064d9bc62
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_oauth_client.cc
@@ -0,0 +1,337 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_oauth_client.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/escape.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
+
+namespace {
+const char kAccessTokenValue[] = "access_token";
+const char kRefreshTokenValue[] = "refresh_token";
+const char kExpiresInValue[] = "expires_in";
+}
+
+namespace gaia {
+
+// Use a non-zero number, so unit tests can differentiate the URLFetcher used by
+// this class from other fetchers (most other code just hardcodes the ID to 0).
+const int GaiaOAuthClient::kUrlFetcherId = 17109006;
+
+class GaiaOAuthClient::Core
+ : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>,
+ public net::URLFetcherDelegate {
+ public:
+ Core(net::URLRequestContextGetter* request_context_getter)
+ : num_retries_(0),
+ request_context_getter_(request_context_getter),
+ delegate_(NULL),
+ request_type_(NO_PENDING_REQUEST) {
+ }
+
+ void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
+ const std::string& auth_code,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate);
+ void RefreshToken(const OAuthClientInfo& oauth_client_info,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate);
+ void GetUserEmail(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+ void GetTokenInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+
+ // net::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ friend class base::RefCountedThreadSafe<Core>;
+
+ enum RequestType {
+ NO_PENDING_REQUEST,
+ TOKENS_FROM_AUTH_CODE,
+ REFRESH_TOKEN,
+ TOKEN_INFO,
+ USER_INFO,
+ };
+
+ virtual ~Core() {}
+
+ void MakeGaiaRequest(const GURL& url,
+ const std::string& post_body,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate);
+ void HandleResponse(const net::URLFetcher* source,
+ bool* should_retry_request);
+
+ int num_retries_;
+ scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
+ GaiaOAuthClient::Delegate* delegate_;
+ scoped_ptr<net::URLFetcher> request_;
+ RequestType request_type_;
+};
+
+void GaiaOAuthClient::Core::GetTokensFromAuthCode(
+ const OAuthClientInfo& oauth_client_info,
+ const std::string& auth_code,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ request_type_ = TOKENS_FROM_AUTH_CODE;
+ std::string post_body =
+ "code=" + net::EscapeUrlEncodedData(auth_code, true) +
+ "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
+ true) +
+ "&client_secret=" +
+ net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
+ "&redirect_uri=" +
+ net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) +
+ "&grant_type=authorization_code";
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
+ post_body, max_retries, delegate);
+}
+
+void GaiaOAuthClient::Core::RefreshToken(
+ const OAuthClientInfo& oauth_client_info,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ request_type_ = REFRESH_TOKEN;
+ std::string post_body =
+ "refresh_token=" + net::EscapeUrlEncodedData(refresh_token, true) +
+ "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id,
+ true) +
+ "&client_secret=" +
+ net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
+ "&grant_type=refresh_token";
+
+ if (!scopes.empty()) {
+ std::string scopes_string = JoinString(scopes, ' ');
+ post_body += "&scope=" + net::EscapeUrlEncodedData(scopes_string, true);
+ }
+
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
+ post_body, max_retries, delegate);
+}
+
+void GaiaOAuthClient::Core::GetUserEmail(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ DCHECK(!request_.get());
+ request_type_ = USER_INFO;
+ delegate_ = delegate;
+ num_retries_ = 0;
+ request_.reset(net::URLFetcher::Create(
+ kUrlFetcherId, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
+ net::URLFetcher::GET, this));
+ request_->SetRequestContext(request_context_getter_.get());
+ request_->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token);
+ request_->SetMaxRetriesOn5xx(max_retries);
+ // Fetchers are sometimes cancelled because a network change was detected,
+ // especially at startup and after sign-in on ChromeOS. Retrying once should
+ // be enough in those cases; let the fetcher retry up to 3 times just in case.
+ // http://crbug.com/163710
+ request_->SetAutomaticallyRetryOnNetworkChanges(3);
+ request_->Start();
+}
+
+void GaiaOAuthClient::Core::GetTokenInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ DCHECK(!request_.get());
+ request_type_ = TOKEN_INFO;
+ std::string post_body =
+ "access_token=" + net::EscapeUrlEncodedData(oauth_access_token, true);
+ MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()),
+ post_body,
+ max_retries,
+ delegate);
+}
+
+void GaiaOAuthClient::Core::MakeGaiaRequest(
+ const GURL& url,
+ const std::string& post_body,
+ int max_retries,
+ GaiaOAuthClient::Delegate* delegate) {
+ DCHECK(!request_.get()) << "Tried to fetch two things at once!";
+ delegate_ = delegate;
+ num_retries_ = 0;
+ request_.reset(net::URLFetcher::Create(
+ kUrlFetcherId, url, net::URLFetcher::POST, this));
+ request_->SetRequestContext(request_context_getter_.get());
+ request_->SetUploadData("application/x-www-form-urlencoded", post_body);
+ request_->SetMaxRetriesOn5xx(max_retries);
+ // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
+ request_->SetAutomaticallyRetryOnNetworkChanges(3);
+ request_->Start();
+}
+
+// URLFetcher::Delegate implementation.
+void GaiaOAuthClient::Core::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ bool should_retry = false;
+ HandleResponse(source, &should_retry);
+ if (should_retry) {
+ // Explicitly call ReceivedContentWasMalformed() to ensure the current
+ // request gets counted as a failure for calculation of the back-off
+ // period. If it was already a failure by status code, this call will
+ // be ignored.
+ request_->ReceivedContentWasMalformed();
+ num_retries_++;
+ // We must set our request_context_getter_ again because
+ // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
+ request_->SetRequestContext(request_context_getter_.get());
+ request_->Start();
+ }
+}
+
+void GaiaOAuthClient::Core::HandleResponse(
+ const net::URLFetcher* source,
+ bool* should_retry_request) {
+ // Move ownership of the request fetcher into a local scoped_ptr which
+ // will be nuked when we're done handling the request, unless we need
+ // to retry, in which case ownership will be returned to request_.
+ scoped_ptr<net::URLFetcher> old_request = request_.Pass();
+ DCHECK_EQ(source, old_request.get());
+
+ // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
+ // done here.
+ if (source->GetResponseCode() == net::HTTP_BAD_REQUEST) {
+ delegate_->OnOAuthError();
+ return;
+ }
+
+ scoped_ptr<base::DictionaryValue> response_dict;
+ if (source->GetResponseCode() == net::HTTP_OK) {
+ std::string data;
+ source->GetResponseAsString(&data);
+ scoped_ptr<base::Value> message_value(base::JSONReader::Read(data));
+ if (message_value.get() &&
+ message_value->IsType(base::Value::TYPE_DICTIONARY)) {
+ response_dict.reset(
+ static_cast<base::DictionaryValue*>(message_value.release()));
+ }
+ }
+
+ if (!response_dict.get()) {
+ // If we don't have an access token yet and the the error was not
+ // RC_BAD_REQUEST, we may need to retry.
+ if ((source->GetMaxRetriesOn5xx() != -1) &&
+ (num_retries_ >= source->GetMaxRetriesOn5xx())) {
+ // Retry limit reached. Give up.
+ delegate_->OnNetworkError(source->GetResponseCode());
+ } else {
+ request_ = old_request.Pass();
+ *should_retry_request = true;
+ }
+ return;
+ }
+
+ RequestType type = request_type_;
+ request_type_ = NO_PENDING_REQUEST;
+
+ switch (type) {
+ case USER_INFO: {
+ std::string email;
+ response_dict->GetString("email", &email);
+ delegate_->OnGetUserEmailResponse(email);
+ break;
+ }
+
+ case TOKEN_INFO: {
+ delegate_->OnGetTokenInfoResponse(response_dict.Pass());
+ break;
+ }
+
+ case TOKENS_FROM_AUTH_CODE:
+ case REFRESH_TOKEN: {
+ std::string access_token;
+ std::string refresh_token;
+ int expires_in_seconds = 0;
+ response_dict->GetString(kAccessTokenValue, &access_token);
+ response_dict->GetString(kRefreshTokenValue, &refresh_token);
+ response_dict->GetInteger(kExpiresInValue, &expires_in_seconds);
+
+ if (access_token.empty()) {
+ delegate_->OnOAuthError();
+ return;
+ }
+
+ if (type == REFRESH_TOKEN) {
+ delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
+ } else {
+ delegate_->OnGetTokensResponse(refresh_token,
+ access_token,
+ expires_in_seconds);
+ }
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+}
+
+GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter* context_getter) {
+ core_ = new Core(context_getter);
+}
+
+GaiaOAuthClient::~GaiaOAuthClient() {
+}
+
+void GaiaOAuthClient::GetTokensFromAuthCode(
+ const OAuthClientInfo& oauth_client_info,
+ const std::string& auth_code,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->GetTokensFromAuthCode(oauth_client_info,
+ auth_code,
+ max_retries,
+ delegate);
+}
+
+void GaiaOAuthClient::RefreshToken(
+ const OAuthClientInfo& oauth_client_info,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->RefreshToken(oauth_client_info,
+ refresh_token,
+ scopes,
+ max_retries,
+ delegate);
+}
+
+void GaiaOAuthClient::GetUserEmail(const std::string& access_token,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->GetUserEmail(access_token, max_retries, delegate);
+}
+
+void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->GetTokenInfo(access_token, max_retries, delegate);
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_oauth_client.h b/chromium/google_apis/gaia/gaia_oauth_client.h
new file mode 100644
index 00000000000..94d33137e84
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_oauth_client.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_OAUTH_CLIENT_H_
+#define GOOGLE_APIS_GAIA_GAIA_OAUTH_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/values.h"
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// A helper class to get and refresh OAuth2 refresh and access tokens.
+// Also exposes utility methods for fetching user email and token information.
+//
+// Supports one request at a time; for parallel requests, create multiple
+// instances.
+namespace gaia {
+
+struct OAuthClientInfo {
+ std::string client_id;
+ std::string client_secret;
+ std::string redirect_uri;
+};
+
+class GaiaOAuthClient {
+ public:
+ const static int kUrlFetcherId;
+
+ class Delegate {
+ public:
+ // Invoked on a successful response to the GetTokensFromAuthCode request.
+ virtual void OnGetTokensResponse(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_seconds) {}
+ // Invoked on a successful response to the RefreshToken request.
+ virtual void OnRefreshTokenResponse(const std::string& access_token,
+ int expires_in_seconds) {}
+ // Invoked on a successful response to the GetUserInfo request.
+ virtual void OnGetUserEmailResponse(const std::string& user_email) {}
+ // Invoked on a successful response to the GetTokenInfo request.
+ virtual void OnGetTokenInfoResponse(
+ scoped_ptr<DictionaryValue> token_info) {}
+ // Invoked when there is an OAuth error with one of the requests.
+ virtual void OnOAuthError() = 0;
+ // Invoked when there is a network error or upon receiving an invalid
+ // response. This is invoked when the maximum number of retries have been
+ // exhausted. If max_retries is -1, this is never invoked.
+ virtual void OnNetworkError(int response_code) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ GaiaOAuthClient(net::URLRequestContextGetter* context_getter);
+ ~GaiaOAuthClient();
+
+ // In the below methods, |max_retries| specifies the maximum number of times
+ // we should retry on a network error in invalid response. This does not
+ // apply in the case of an OAuth error (i.e. there was something wrong with
+ // the input arguments). Setting |max_retries| to -1 implies infinite retries.
+
+ // Given an OAuth2 authorization code, fetch the long-lived refresh token
+ // and a valid access token. After the access token expires, RefreshToken()
+ // can be used to fetch a fresh access token. See |max_retries| docs above.
+ void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
+ const std::string& auth_code,
+ int max_retries,
+ Delegate* delegate);
+
+ // Given a valid refresh token (usually fetched via
+ // |GetTokensFromAuthCode()|), fetch a fresh access token that can be used
+ // to authenticate an API call. If |scopes| is non-empty, then fetch an
+ // access token for those specific scopes (assuming the refresh token has the
+ // appropriate permissions). See |max_retries| docs above.
+ void RefreshToken(const OAuthClientInfo& oauth_client_info,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes,
+ int max_retries,
+ Delegate* delegate);
+
+ // Call the userinfo API, returning the user email address associated
+ // with the given access token. The provided access token must have
+ // https://www.googleapis.com/auth/userinfo.email as one of its scopes.
+ // See |max_retries| docs above.
+ void GetUserEmail(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+
+ // Call the tokeninfo API, returning a dictionary of response values. The
+ // provided access token may have any scope, and basic results will be
+ // returned: issued_to, audience, scope, expires_in, access_type. In
+ // addition, if the https://www.googleapis.com/auth/userinfo.email scope is
+ // present, the email and verified_email fields will be returned. If the
+ // https://www.googleapis.com/auth/userinfo.profile scope is present, the
+ // user_id field will be returned. See |max_retries| docs above.
+ void GetTokenInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+
+ private:
+ // The guts of the implementation live in this class.
+ class Core;
+ scoped_refptr<Core> core_;
+ DISALLOW_COPY_AND_ASSIGN(GaiaOAuthClient);
+};
+}
+
+#endif // GOOGLE_APIS_GAIA_GAIA_OAUTH_CLIENT_H_
diff --git a/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc b/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc
new file mode 100644
index 00000000000..cdeb4831154
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_oauth_client_unittest.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 2012 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.
+//
+// A complete set of unit tests for GaiaOAuthClient.
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Pointee;
+using ::testing::SaveArg;
+
+namespace {
+
+// Responds as though OAuth returned from the server.
+class MockOAuthFetcher : public net::TestURLFetcher {
+ public:
+ MockOAuthFetcher(int response_code,
+ int max_failure_count,
+ bool complete_immediately,
+ const GURL& url,
+ const std::string& results,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d)
+ : net::TestURLFetcher(0, url, d),
+ max_failure_count_(max_failure_count),
+ current_failure_count_(0),
+ complete_immediately_(complete_immediately) {
+ set_url(url);
+ set_response_code(response_code);
+ SetResponseString(results);
+ }
+
+ virtual ~MockOAuthFetcher() { }
+
+ virtual void Start() OVERRIDE {
+ if ((GetResponseCode() != net::HTTP_OK) && (max_failure_count_ != -1) &&
+ (current_failure_count_ == max_failure_count_)) {
+ set_response_code(net::HTTP_OK);
+ }
+
+ net::URLRequestStatus::Status code = net::URLRequestStatus::SUCCESS;
+ if (GetResponseCode() != net::HTTP_OK) {
+ code = net::URLRequestStatus::FAILED;
+ current_failure_count_++;
+ }
+ set_status(net::URLRequestStatus(code, 0));
+
+ if (complete_immediately_)
+ delegate()->OnURLFetchComplete(this);
+ }
+
+ void Finish() {
+ ASSERT_FALSE(complete_immediately_);
+ delegate()->OnURLFetchComplete(this);
+ }
+
+ private:
+ int max_failure_count_;
+ int current_failure_count_;
+ bool complete_immediately_;
+ DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcher);
+};
+
+class MockOAuthFetcherFactory : public net::URLFetcherFactory,
+ public net::ScopedURLFetcherFactory {
+ public:
+ MockOAuthFetcherFactory()
+ : net::ScopedURLFetcherFactory(this),
+ response_code_(net::HTTP_OK),
+ complete_immediately_(true) {
+ }
+ virtual ~MockOAuthFetcherFactory() {}
+ virtual net::URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d) OVERRIDE {
+ url_fetcher_ = new MockOAuthFetcher(
+ response_code_,
+ max_failure_count_,
+ complete_immediately_,
+ url,
+ results_,
+ request_type,
+ d);
+ return url_fetcher_;
+ }
+ void set_response_code(int response_code) {
+ response_code_ = response_code;
+ }
+ void set_max_failure_count(int count) {
+ max_failure_count_ = count;
+ }
+ void set_results(const std::string& results) {
+ results_ = results;
+ }
+ MockOAuthFetcher* get_url_fetcher() {
+ return url_fetcher_;
+ }
+ void set_complete_immediately(bool complete_immediately) {
+ complete_immediately_ = complete_immediately;
+ }
+ private:
+ MockOAuthFetcher* url_fetcher_;
+ int response_code_;
+ bool complete_immediately_;
+ int max_failure_count_;
+ std::string results_;
+ DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcherFactory);
+};
+
+const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg";
+const std::string kTestRefreshToken =
+ "1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ";
+const std::string kTestUserEmail = "a_user@gmail.com";
+const int kTestExpiresIn = 3920;
+
+const std::string kDummyGetTokensResult =
+ "{\"access_token\":\"" + kTestAccessToken + "\","
+ "\"expires_in\":" + base::IntToString(kTestExpiresIn) + ","
+ "\"refresh_token\":\"" + kTestRefreshToken + "\"}";
+
+const std::string kDummyRefreshTokenResult =
+ "{\"access_token\":\"" + kTestAccessToken + "\","
+ "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}";
+
+const std::string kDummyUserInfoResult =
+ "{\"email\":\"" + kTestUserEmail + "\"}";
+
+const std::string kDummyTokenInfoResult =
+ "{\"issued_to\": \"1234567890.apps.googleusercontent.com\","
+ "\"audience\": \"1234567890.apps.googleusercontent.com\","
+ "\"scope\": \"https://googleapis.com/oauth2/v2/tokeninfo\","
+ "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}";
+}
+
+namespace gaia {
+
+class GaiaOAuthClientTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ client_info_.client_id = "test_client_id";
+ client_info_.client_secret = "test_client_secret";
+ client_info_.redirect_uri = "test_redirect_uri";
+ };
+
+ protected:
+ net::TestURLRequestContextGetter* GetRequestContext() {
+ if (!request_context_getter_) {
+ request_context_getter_ = new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy());
+ }
+ return request_context_getter_;
+ }
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+ OAuthClientInfo client_info_;
+};
+
+class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
+ public:
+ MockGaiaOAuthClientDelegate() {}
+ ~MockGaiaOAuthClientDelegate() {}
+
+ MOCK_METHOD3(OnGetTokensResponse, void(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_seconds));
+ MOCK_METHOD2(OnRefreshTokenResponse, void(const std::string& access_token,
+ int expires_in_seconds));
+ MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email));
+ MOCK_METHOD0(OnOAuthError, void());
+ MOCK_METHOD1(OnNetworkError, void(int response_code));
+
+ // gMock doesn't like methods that take or return scoped_ptr. A
+ // work-around is to create a mock method that takes a raw ptr, and
+ // override the problematic method to call through to it.
+ // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ
+ MOCK_METHOD1(OnGetTokenInfoResponsePtr,
+ void(const DictionaryValue* token_info));
+ virtual void OnGetTokenInfoResponse(scoped_ptr<DictionaryValue> token_info)
+ OVERRIDE {
+ token_info_.reset(token_info.release());
+ OnGetTokenInfoResponsePtr(token_info_.get());
+ }
+
+ private:
+ scoped_ptr<DictionaryValue> token_info_;
+ DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate);
+};
+
+TEST_F(GaiaOAuthClientTest, NetworkFailure) {
+ int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
+
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnNetworkError(response_code))
+ .Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_response_code(response_code);
+ factory.set_max_failure_count(4);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &delegate);
+}
+
+TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) {
+ int response_code = net::HTTP_INTERNAL_SERVER_ERROR;
+
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
+ kTestExpiresIn)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_response_code(response_code);
+ factory.set_max_failure_count(4);
+ factory.set_results(kDummyGetTokensResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
+}
+
+TEST_F(GaiaOAuthClientTest, OAuthFailure) {
+ int response_code = net::HTTP_BAD_REQUEST;
+
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnOAuthError()).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_response_code(response_code);
+ factory.set_max_failure_count(-1);
+ factory.set_results(kDummyGetTokensResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
+}
+
+
+TEST_F(GaiaOAuthClientTest, GetTokensSuccess) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken,
+ kTestExpiresIn)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyGetTokensResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate);
+}
+
+TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
+ kTestExpiresIn)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyRefreshTokenResult);
+ factory.set_complete_immediately(false);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.RefreshToken(client_info_, "refresh_token", std::vector<std::string>(),
+ -1, &delegate);
+ EXPECT_THAT(factory.get_url_fetcher()->upload_data(),
+ Not(HasSubstr("scope")));
+ factory.get_url_fetcher()->Finish();
+}
+
+TEST_F(GaiaOAuthClientTest, RefreshTokenDownscopingSuccess) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken,
+ kTestExpiresIn)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyRefreshTokenResult);
+ factory.set_complete_immediately(false);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.RefreshToken(client_info_, "refresh_token",
+ std::vector<std::string>(1, "scope4test"), -1, &delegate);
+ EXPECT_THAT(factory.get_url_fetcher()->upload_data(),
+ HasSubstr("&scope=scope4test"));
+ factory.get_url_fetcher()->Finish();
+}
+
+
+TEST_F(GaiaOAuthClientTest, GetUserEmail) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetUserEmailResponse(kTestUserEmail)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyUserInfoResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetUserEmail("access_token", 1, &delegate);
+}
+
+TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
+ const DictionaryValue* captured_result;
+
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_))
+ .WillOnce(SaveArg<0>(&captured_result));
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyTokenInfoResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetTokenInfo("access_token", 1, &delegate);
+
+ std::string issued_to;
+ ASSERT_TRUE(captured_result->GetString("issued_to", &issued_to));
+ ASSERT_EQ("1234567890.apps.googleusercontent.com", issued_to);
+}
+
+} // namespace gaia
diff --git a/chromium/google_apis/gaia/gaia_switches.cc b/chromium/google_apis/gaia/gaia_switches.cc
new file mode 100644
index 00000000000..c2430b0b737
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_switches.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_switches.h"
+
+namespace switches {
+
+const char kClientLoginToOAuth2Url[] = "client-login-to-oauth2-url";
+const char kGaiaUrl[] = "gaia-url";
+const char kGoogleApisHost[] = "google-apis-host";
+const char kLsoUrl[] = "lso-url";
+const char kOAuth1LoginScope[] = "oauth1-login-scope";
+const char kOAuth2IssueTokenUrl[] = "oauth2-issue-token-url";
+const char kOAuth2TokenUrl[] = "oauth2-token-url";
+const char kOAuthUserInfoUrl[] = "oauth-user-info-url";
+
+} // namespace switches
diff --git a/chromium/google_apis/gaia/gaia_switches.h b/chromium/google_apis/gaia/gaia_switches.h
new file mode 100644
index 00000000000..727a35557a9
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_switches.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_
+#define GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_
+
+namespace switches {
+
+// Supplies custom client login to OAuth2 URL for testing purposes.
+extern const char kClientLoginToOAuth2Url[];
+
+// Specifies the path for GAIA authentication URL. The default value is
+// "https://accounts.google.com".
+extern const char kGaiaUrl[];
+
+// Specifies the backend server used for Google API calls. The https:// prefix
+// and the trailing slash should be omitted.
+// The default value is "www.googleapis.com".
+extern const char kGoogleApisHost[];
+
+// Specifies the backend server used for lso authentication calls.
+// "https://accounts.google.com".
+extern const char kLsoUrl[];
+
+// TODO(zelidrag): Get rid of all following since all URLs should be
+// controlled only with --gaia-host, --lso-host and --google-apis-host.
+
+// Specifies custom OAuth1 login scope for testing purposes.
+extern const char kOAuth1LoginScope[];
+
+// Specifies custom OAuth2 issue token URL for testing purposes.
+extern const char kOAuth2IssueTokenUrl[];
+
+// Specifies custom OAuth2 token URL for testing purposes.
+extern const char kOAuth2TokenUrl[];
+
+// Specifies custom OAuth user info URL for testing purposes.
+extern const char kOAuthUserInfoUrl[];
+} // namespace switches
+
+#endif // GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_
diff --git a/chromium/google_apis/gaia/gaia_urls.cc b/chromium/google_apis/gaia/gaia_urls.cc
new file mode 100644
index 00000000000..578205cb09b
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_urls.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/gaia_urls.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "google_apis/gaia/gaia_switches.h"
+#include "google_apis/google_api_keys.h"
+
+namespace {
+
+// Gaia service constants
+const char kDefaultGaiaUrl[] = "https://accounts.google.com";
+const char kDefaultGoogleApisBaseUrl[] = "www.googleapis.com";
+const char kCaptchaUrlPrefixSuffix[] = "/";
+
+// API calls from accounts.google.com
+const char kClientLoginUrlSuffix[] = "/ClientLogin";
+const char kServiceLoginUrlSuffix[] = "/ServiceLogin";
+const char kServiceLogoutUrlSuffix[] = "/Logout";
+const char kIssueAuthTokenUrlSuffix[] = "/IssueAuthToken";
+const char kGetUserInfoUrlSuffix[] = "/GetUserInfo";
+const char kTokenAuthUrlSuffix[] = "/TokenAuth";
+const char kMergeSessionUrlSuffix[] = "/MergeSession";
+const char kOAuthGetAccessTokenUrlSuffix[] = "/OAuthGetAccessToken";
+const char kOAuthWrapBridgeUrlSuffix[] = "/OAuthWrapBridge";
+const char kOAuth1LoginUrlSuffix[] = "/OAuthLogin";
+const char kOAuthRevokeTokenUrlSuffix[] = "/AuthSubRevokeToken";
+
+// API calls from accounts.google.com (LSO)
+const char kGetOAuthTokenUrlSuffix[] = "/o/oauth/GetOAuthToken/";
+const char kClientLoginToOAuth2UrlSuffix[] = "/o/oauth2/programmatic_auth";
+const char kOAuth2AuthUrlSuffix[] = "/o/oauth2/auth";
+const char kOAuth2RevokeUrlSuffix[] = "/o/oauth2/revoke";
+const char kOAuth2TokenUrlSuffix[] = "/o/oauth2/token";
+const char kClientOAuthUrlSuffix[] = "/ClientOAuth";
+
+// API calls from www.googleapis.com
+const char kOAuth2IssueTokenUrlSuffix[] = "/oauth2/v2/IssueToken";
+const char kOAuth2TokenInfoUrlSuffix[] = "/oauth2/v2/tokeninfo";
+const char kOAuthUserInfoUrlSuffix[] = "/oauth2/v1/userinfo";
+const char kOAuthWrapBridgeUserInfoScopeUrlSuffix[] = "/auth/userinfo.email";
+
+const char kOAuth1LoginScope[] =
+ "https://www.google.com/accounts/OAuthLogin";
+
+void GetSwitchValueWithDefault(const char* switch_value,
+ const char* default_value,
+ std::string* output_value) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switch_value)) {
+ *output_value = command_line->GetSwitchValueASCII(switch_value);
+ } else {
+ *output_value = default_value;
+ }
+}
+
+} // namespace
+
+GaiaUrls* GaiaUrls::GetInstance() {
+ return Singleton<GaiaUrls>::get();
+}
+
+GaiaUrls::GaiaUrls() {
+ std::string gaia_url_str;
+ GetSwitchValueWithDefault(switches::kGaiaUrl,
+ kDefaultGaiaUrl,
+ &gaia_url_str);
+ gaia_url_ = GURL(gaia_url_str);
+ DCHECK(gaia_url_.is_valid());
+
+ GetSwitchValueWithDefault(switches::kLsoUrl,
+ kDefaultGaiaUrl,
+ &lso_origin_url_);
+
+ std::string google_apis_base;
+ GetSwitchValueWithDefault(switches::kGoogleApisHost,
+ kDefaultGoogleApisBaseUrl,
+ &google_apis_base);
+
+ captcha_url_prefix_ = "http://" + gaia_url_.host() +
+ (gaia_url_.has_port() ? ":" + gaia_url_.port() : "") +
+ kCaptchaUrlPrefixSuffix;
+
+ google_apis_origin_url_ = "https://" + google_apis_base;
+
+ oauth2_chrome_client_id_ =
+ google_apis::GetOAuth2ClientID(google_apis::CLIENT_MAIN);
+ oauth2_chrome_client_secret_ =
+ google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_MAIN);
+
+ // URLs from accounts.google.com.
+ gaia_login_form_realm_ = gaia_url_str + "/";
+ client_login_url_ = gaia_url_str + kClientLoginUrlSuffix;
+ service_login_url_ = gaia_url_str + kServiceLoginUrlSuffix;
+ service_logout_url_ = gaia_url_str + kServiceLogoutUrlSuffix;
+ issue_auth_token_url_ = gaia_url_str + kIssueAuthTokenUrlSuffix;
+ get_user_info_url_ = gaia_url_str + kGetUserInfoUrlSuffix;
+ token_auth_url_ = gaia_url_str + kTokenAuthUrlSuffix;
+ merge_session_url_ = gaia_url_str + kMergeSessionUrlSuffix;
+ oauth_get_access_token_url_ = gaia_url_str + kOAuthGetAccessTokenUrlSuffix;
+ oauth_wrap_bridge_url_ = gaia_url_str + kOAuthWrapBridgeUrlSuffix;
+ oauth_revoke_token_url_ = gaia_url_str + kOAuthRevokeTokenUrlSuffix;
+ oauth1_login_url_ = gaia_url_str + kOAuth1LoginUrlSuffix;
+
+ // URLs from accounts.google.com (LSO).
+ get_oauth_token_url_ = lso_origin_url_ + kGetOAuthTokenUrlSuffix;
+ std::string client_login_to_oauth2_url = lso_origin_url_ +
+ kClientLoginToOAuth2UrlSuffix;
+ oauth2_auth_url_ = lso_origin_url_ + kOAuth2AuthUrlSuffix;
+ std::string oauth2_token_url = lso_origin_url_ + kOAuth2TokenUrlSuffix;
+ oauth2_revoke_url_ = lso_origin_url_ + kOAuth2RevokeUrlSuffix;
+
+ // URLs from www.googleapis.com.
+ oauth_wrap_bridge_user_info_scope_ = google_apis_origin_url_ +
+ kOAuthWrapBridgeUserInfoScopeUrlSuffix;
+ std::string oauth2_issue_token_url = google_apis_origin_url_ +
+ kOAuth2IssueTokenUrlSuffix;
+ oauth2_token_info_url_ = google_apis_origin_url_ + kOAuth2TokenInfoUrlSuffix;
+ std::string oauth_user_info_url = google_apis_origin_url_ +
+ kOAuthUserInfoUrlSuffix;
+
+ // TODO(zelidrag): Get rid of all these switches since all URLs should be
+ // controlled only with --gaia-url, --lso-url and --google-apis-host.
+ GetSwitchValueWithDefault(switches::kOAuth1LoginScope,
+ kOAuth1LoginScope,
+ &oauth1_login_scope_);
+ GetSwitchValueWithDefault(switches::kClientLoginToOAuth2Url,
+ client_login_to_oauth2_url.c_str(),
+ &client_login_to_oauth2_url_);
+ GetSwitchValueWithDefault(switches::kOAuth2TokenUrl,
+ oauth2_token_url.c_str(),
+ &oauth2_token_url_);
+ GetSwitchValueWithDefault(switches::kOAuth2IssueTokenUrl,
+ oauth2_issue_token_url.c_str(),
+ &oauth2_issue_token_url_);
+ GetSwitchValueWithDefault(switches::kOAuthUserInfoUrl,
+ oauth_user_info_url.c_str(),
+ &oauth_user_info_url_);
+}
+
+GaiaUrls::~GaiaUrls() {
+}
+
+const std::string& GaiaUrls::captcha_url_prefix() const {
+ return captcha_url_prefix_;
+}
+
+const GURL& GaiaUrls::gaia_url() const {
+ return gaia_url_;
+}
+
+const std::string& GaiaUrls::client_login_url() const {
+ return client_login_url_;
+}
+
+const std::string& GaiaUrls::service_login_url() const {
+ return service_login_url_;
+}
+
+const std::string& GaiaUrls::service_logout_url() const {
+ return service_logout_url_;
+}
+
+const std::string& GaiaUrls::issue_auth_token_url() const {
+ return issue_auth_token_url_;
+}
+
+const std::string& GaiaUrls::get_user_info_url() const {
+ return get_user_info_url_;
+}
+
+const std::string& GaiaUrls::token_auth_url() const {
+ return token_auth_url_;
+}
+
+const std::string& GaiaUrls::merge_session_url() const {
+ return merge_session_url_;
+}
+
+const std::string& GaiaUrls::get_oauth_token_url() const {
+ return get_oauth_token_url_;
+}
+
+const std::string& GaiaUrls::oauth_get_access_token_url() const {
+ return oauth_get_access_token_url_;
+}
+
+const std::string& GaiaUrls::oauth_wrap_bridge_url() const {
+ return oauth_wrap_bridge_url_;
+}
+
+const std::string& GaiaUrls::oauth_user_info_url() const {
+ return oauth_user_info_url_;
+}
+
+const std::string& GaiaUrls::oauth_revoke_token_url() const {
+ return oauth_revoke_token_url_;
+}
+
+const std::string& GaiaUrls::oauth1_login_url() const {
+ return oauth1_login_url_;
+}
+
+const std::string& GaiaUrls::oauth1_login_scope() const {
+ return oauth1_login_scope_;
+}
+
+const std::string& GaiaUrls::oauth_wrap_bridge_user_info_scope() const {
+ return oauth_wrap_bridge_user_info_scope_;
+}
+
+const std::string& GaiaUrls::oauth2_chrome_client_id() const {
+ return oauth2_chrome_client_id_;
+}
+
+const std::string& GaiaUrls::oauth2_chrome_client_secret() const {
+ return oauth2_chrome_client_secret_;
+}
+
+const std::string& GaiaUrls::client_login_to_oauth2_url() const {
+ return client_login_to_oauth2_url_;
+}
+
+const std::string& GaiaUrls::oauth2_auth_url() const {
+ return oauth2_auth_url_;
+}
+
+const std::string& GaiaUrls::oauth2_token_url() const {
+ return oauth2_token_url_;
+}
+
+const std::string& GaiaUrls::oauth2_issue_token_url() const {
+ return oauth2_issue_token_url_;
+}
+
+const std::string& GaiaUrls::oauth2_token_info_url() const {
+ return oauth2_token_info_url_;
+}
+
+const std::string& GaiaUrls::oauth2_revoke_url() const {
+ return oauth2_revoke_url_;
+}
+
+const std::string& GaiaUrls::gaia_login_form_realm() const {
+ return gaia_login_form_realm_;
+}
diff --git a/chromium/google_apis/gaia/gaia_urls.h b/chromium/google_apis/gaia/gaia_urls.h
new file mode 100644
index 00000000000..28c72792f0b
--- /dev/null
+++ b/chromium/google_apis/gaia/gaia_urls.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_GAIA_URLS_H_
+#define GOOGLE_APIS_GAIA_GAIA_URLS_H_
+
+#include <string>
+
+#include "base/memory/singleton.h"
+#include "url/gurl.h"
+
+// A signleton that provides all the URLs that are used for connecting to GAIA.
+class GaiaUrls {
+ public:
+ static GaiaUrls* GetInstance();
+
+ // The URLs for different calls in the Google Accounts programmatic login API.
+ const std::string& captcha_url_prefix() const;
+
+ const GURL& gaia_url() const;
+ const std::string& client_login_url() const;
+ const std::string& service_login_url() const;
+ const std::string& service_logout_url() const;
+ const std::string& issue_auth_token_url() const;
+ const std::string& get_user_info_url() const;
+ const std::string& token_auth_url() const;
+ const std::string& merge_session_url() const;
+ const std::string& get_oauth_token_url() const;
+ const std::string& oauth_get_access_token_url() const;
+ const std::string& oauth_wrap_bridge_url() const;
+ const std::string& oauth_user_info_url() const;
+ const std::string& oauth_revoke_token_url() const;
+ const std::string& oauth1_login_url() const;
+
+ const std::string& oauth1_login_scope() const;
+ const std::string& oauth_wrap_bridge_user_info_scope() const;
+
+ const std::string& oauth2_chrome_client_id() const;
+ const std::string& oauth2_chrome_client_secret() const;
+ const std::string& client_login_to_oauth2_url() const;
+ const std::string& oauth2_auth_url() const;
+ const std::string& oauth2_token_url() const;
+ const std::string& oauth2_issue_token_url() const;
+ const std::string& oauth2_token_info_url() const;
+ const std::string& oauth2_revoke_url() const;
+
+ const std::string& gaia_login_form_realm() const;
+
+ private:
+ GaiaUrls();
+ ~GaiaUrls();
+
+ friend struct DefaultSingletonTraits<GaiaUrls>;
+
+ std::string captcha_url_prefix_;
+
+ GURL gaia_url_;
+ std::string lso_origin_url_;
+ std::string google_apis_origin_url_;
+ std::string client_login_url_;
+ std::string service_login_url_;
+ std::string service_logout_url_;
+ std::string issue_auth_token_url_;
+ std::string get_user_info_url_;
+ std::string token_auth_url_;
+ std::string merge_session_url_;
+ std::string get_oauth_token_url_;
+ std::string oauth_get_access_token_url_;
+ std::string oauth_wrap_bridge_url_;
+ std::string oauth_user_info_url_;
+ std::string oauth_revoke_token_url_;
+ std::string oauth1_login_url_;
+
+ std::string oauth1_login_scope_;
+ std::string oauth_wrap_bridge_user_info_scope_;
+
+ std::string oauth2_chrome_client_id_;
+ std::string oauth2_chrome_client_secret_;
+ std::string client_login_to_oauth2_url_;
+ std::string oauth2_auth_url_;
+ std::string oauth2_token_url_;
+ std::string oauth2_issue_token_url_;
+ std::string oauth2_token_info_url_;
+ std::string oauth2_revoke_url_;
+
+ std::string gaia_login_form_realm_;
+
+ DISALLOW_COPY_AND_ASSIGN(GaiaUrls);
+};
+
+#endif // GOOGLE_APIS_GAIA_GAIA_URLS_H_
diff --git a/chromium/google_apis/gaia/google_service_auth_error.cc b/chromium/google_apis/gaia/google_service_auth_error.cc
new file mode 100644
index 00000000000..ab3d9c07de5
--- /dev/null
+++ b/chromium/google_apis/gaia/google_service_auth_error.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/google_service_auth_error.h"
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+
+GoogleServiceAuthError::Captcha::Captcha() : image_width(0), image_height(0) {
+}
+
+GoogleServiceAuthError::Captcha::Captcha(
+ const std::string& token, const GURL& audio, const GURL& img,
+ const GURL& unlock, int width, int height)
+ : token(token), audio_url(audio), image_url(img), unlock_url(unlock),
+ image_width(width), image_height(height) {
+}
+
+GoogleServiceAuthError::Captcha::~Captcha() {
+}
+
+bool GoogleServiceAuthError::Captcha::operator==(const Captcha& b) const {
+ return (token == b.token &&
+ audio_url == b.audio_url &&
+ image_url == b.image_url &&
+ unlock_url == b.unlock_url &&
+ image_width == b.image_width &&
+ image_height == b.image_height);
+}
+
+GoogleServiceAuthError::SecondFactor::SecondFactor() : field_length(0) {
+}
+
+GoogleServiceAuthError::SecondFactor::SecondFactor(
+ const std::string& token, const std::string& prompt,
+ const std::string& alternate, int length)
+ : token(token), prompt_text(prompt), alternate_text(alternate),
+ field_length(length) {
+}
+
+GoogleServiceAuthError::SecondFactor::~SecondFactor() {
+}
+
+bool GoogleServiceAuthError::SecondFactor::operator==(
+ const SecondFactor& b) const {
+ return (token == b.token &&
+ prompt_text == b.prompt_text &&
+ alternate_text == b.alternate_text &&
+ field_length == b.field_length);
+}
+
+bool GoogleServiceAuthError::operator==(
+ const GoogleServiceAuthError& b) const {
+ return (state_ == b.state_ &&
+ network_error_ == b.network_error_ &&
+ captcha_ == b.captcha_ &&
+ second_factor_ == b.second_factor_);
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(State s)
+ : state_(s),
+ network_error_(0) {
+ // If the caller has no idea, then we just set it to a generic failure.
+ if (s == CONNECTION_FAILED) {
+ network_error_ = net::ERR_FAILED;
+ }
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(
+ State state,
+ const std::string& error_message)
+ : state_(state),
+ network_error_(0),
+ error_message_(error_message) {
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(const std::string& error_message)
+ : state_(INVALID_GAIA_CREDENTIALS),
+ network_error_(0),
+ error_message_(error_message) {
+}
+
+// static
+GoogleServiceAuthError
+ GoogleServiceAuthError::FromConnectionError(int error) {
+ return GoogleServiceAuthError(CONNECTION_FAILED, error);
+}
+
+// static
+GoogleServiceAuthError GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
+ const std::string& captcha_token,
+ const GURL& captcha_image_url,
+ const GURL& captcha_unlock_url) {
+ return GoogleServiceAuthError(CAPTCHA_REQUIRED, captcha_token, GURL(),
+ captcha_image_url, captcha_unlock_url, 0, 0);
+}
+
+// static
+GoogleServiceAuthError GoogleServiceAuthError::FromServiceError(
+ const std::string& error_message) {
+ return GoogleServiceAuthError(SERVICE_ERROR, error_message);
+}
+
+// static
+GoogleServiceAuthError GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ const std::string& error_message) {
+ return GoogleServiceAuthError(UNEXPECTED_SERVICE_RESPONSE, error_message);
+}
+
+// static
+GoogleServiceAuthError GoogleServiceAuthError::AuthErrorNone() {
+ return GoogleServiceAuthError(NONE);
+}
+
+GoogleServiceAuthError::State GoogleServiceAuthError::state() const {
+ return state_;
+}
+
+const GoogleServiceAuthError::Captcha& GoogleServiceAuthError::captcha() const {
+ return captcha_;
+}
+
+const GoogleServiceAuthError::SecondFactor&
+GoogleServiceAuthError::second_factor() const {
+ return second_factor_;
+}
+
+int GoogleServiceAuthError::network_error() const {
+ return network_error_;
+}
+
+const std::string& GoogleServiceAuthError::token() const {
+ switch (state_) {
+ case CAPTCHA_REQUIRED:
+ return captcha_.token;
+ break;
+ case TWO_FACTOR:
+ return second_factor_.token;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return EmptyString();
+}
+
+const std::string& GoogleServiceAuthError::error_message() const {
+ return error_message_;
+}
+
+base::DictionaryValue* GoogleServiceAuthError::ToValue() const {
+ base::DictionaryValue* value = new base::DictionaryValue();
+ std::string state_str;
+ switch (state_) {
+#define STATE_CASE(x) case x: state_str = #x; break
+ STATE_CASE(NONE);
+ STATE_CASE(INVALID_GAIA_CREDENTIALS);
+ STATE_CASE(USER_NOT_SIGNED_UP);
+ STATE_CASE(CONNECTION_FAILED);
+ STATE_CASE(CAPTCHA_REQUIRED);
+ STATE_CASE(ACCOUNT_DELETED);
+ STATE_CASE(ACCOUNT_DISABLED);
+ STATE_CASE(SERVICE_UNAVAILABLE);
+ STATE_CASE(TWO_FACTOR);
+ STATE_CASE(REQUEST_CANCELED);
+ STATE_CASE(HOSTED_NOT_ALLOWED);
+ STATE_CASE(UNEXPECTED_SERVICE_RESPONSE);
+ STATE_CASE(SERVICE_ERROR);
+#undef STATE_CASE
+ default:
+ NOTREACHED();
+ break;
+ }
+ value->SetString("state", state_str);
+ if (!error_message_.empty()) {
+ value->SetString("errorMessage", error_message_);
+ }
+ if (state_ == CAPTCHA_REQUIRED) {
+ base::DictionaryValue* captcha_value = new base::DictionaryValue();
+ value->Set("captcha", captcha_value);
+ captcha_value->SetString("token", captcha_.token);
+ captcha_value->SetString("audioUrl", captcha_.audio_url.spec());
+ captcha_value->SetString("imageUrl", captcha_.image_url.spec());
+ captcha_value->SetString("unlockUrl", captcha_.unlock_url.spec());
+ captcha_value->SetInteger("imageWidth", captcha_.image_width);
+ captcha_value->SetInteger("imageHeight", captcha_.image_height);
+ } else if (state_ == CONNECTION_FAILED) {
+ value->SetString("networkError", net::ErrorToString(network_error_));
+ } else if (state_ == TWO_FACTOR) {
+ base::DictionaryValue* two_factor_value = new base::DictionaryValue();
+ value->Set("two_factor", two_factor_value);
+ two_factor_value->SetString("token", second_factor_.token);
+ two_factor_value->SetString("promptText", second_factor_.prompt_text);
+ two_factor_value->SetString("alternateText", second_factor_.alternate_text);
+ two_factor_value->SetInteger("fieldLength", second_factor_.field_length);
+ }
+ return value;
+}
+
+std::string GoogleServiceAuthError::ToString() const {
+ switch (state_) {
+ case NONE:
+ return std::string();
+ case INVALID_GAIA_CREDENTIALS:
+ return "Invalid credentials.";
+ case USER_NOT_SIGNED_UP:
+ return "Not authorized.";
+ case CONNECTION_FAILED:
+ return base::StringPrintf("Connection failed (%d).", network_error_);
+ case CAPTCHA_REQUIRED:
+ return base::StringPrintf("CAPTCHA required (%s).",
+ captcha_.token.c_str());
+ case ACCOUNT_DELETED:
+ return "Account deleted.";
+ case ACCOUNT_DISABLED:
+ return "Account disabled.";
+ case SERVICE_UNAVAILABLE:
+ return "Service unavailable; try again later.";
+ case TWO_FACTOR:
+ return base::StringPrintf("2-step verification required (%s).",
+ second_factor_.token.c_str());
+ case REQUEST_CANCELED:
+ return "Request canceled.";
+ case HOSTED_NOT_ALLOWED:
+ return "Google account required.";
+ case UNEXPECTED_SERVICE_RESPONSE:
+ return base::StringPrintf("Unexpected service response (%s)",
+ error_message_.c_str());
+ case SERVICE_ERROR:
+ return base::StringPrintf("Service responded with error: '%s'",
+ error_message_.c_str());
+ default:
+ NOTREACHED();
+ return std::string();
+ }
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(State s, int error)
+ : state_(s),
+ network_error_(error) {
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(
+ State s,
+ const std::string& captcha_token,
+ const GURL& captcha_audio_url,
+ const GURL& captcha_image_url,
+ const GURL& captcha_unlock_url,
+ int image_width,
+ int image_height)
+ : state_(s),
+ captcha_(captcha_token, captcha_audio_url, captcha_image_url,
+ captcha_unlock_url, image_width, image_height),
+ network_error_(0) {
+}
+
+GoogleServiceAuthError::GoogleServiceAuthError(
+ State s,
+ const std::string& captcha_token,
+ const std::string& prompt_text,
+ const std::string& alternate_text,
+ int field_length)
+ : state_(s),
+ second_factor_(captcha_token, prompt_text, alternate_text, field_length),
+ network_error_(0) {
+}
diff --git a/chromium/google_apis/gaia/google_service_auth_error.h b/chromium/google_apis/gaia/google_service_auth_error.h
new file mode 100644
index 00000000000..a99e515f31d
--- /dev/null
+++ b/chromium/google_apis/gaia/google_service_auth_error.h
@@ -0,0 +1,213 @@
+// Copyright (c) 2012 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.
+
+// A GoogleServiceAuthError is immutable, plain old data representing an
+// error from an attempt to authenticate with a Google service.
+// It could be from Google Accounts itself, or any service using Google
+// Accounts (e.g expired credentials). It may contain additional data such as
+// captcha or OTP challenges.
+
+// A GoogleServiceAuthError without additional data is just a State, defined
+// below. A case could be made to have this relation implicit, to allow raising
+// error events concisely by doing OnAuthError(GoogleServiceAuthError::NONE),
+// for example. But the truth is this class is ever so slightly more than a
+// transparent wrapper around 'State' due to additional Captcha data
+// (e.g consider operator=), and this would violate the style guide. Thus,
+// you must explicitly use the constructor when all you have is a State.
+// The good news is the implementation nests the enum inside a class, so you
+// may forward declare and typedef GoogleServiceAuthError to something shorter
+// in the comfort of your own translation unit.
+
+#ifndef GOOGLE_APIS_GAIA_GOOGLE_SERVICE_AUTH_ERROR_H_
+#define GOOGLE_APIS_GAIA_GOOGLE_SERVICE_AUTH_ERROR_H_
+
+#include <string>
+
+#include "url/gurl.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class GoogleServiceAuthError {
+ public:
+ //
+ // These enumerations are referenced by integer value in HTML login code.
+ // Do not change the numeric values.
+ //
+ enum State {
+ // The user is authenticated.
+ NONE = 0,
+
+ // The credentials supplied to GAIA were either invalid, or the locally
+ // cached credentials have expired.
+ INVALID_GAIA_CREDENTIALS = 1,
+
+ // The GAIA user is not authorized to use the service.
+ USER_NOT_SIGNED_UP = 2,
+
+ // Could not connect to server to verify credentials. This could be in
+ // response to either failure to connect to GAIA or failure to connect to
+ // the service needing GAIA tokens during authentication.
+ CONNECTION_FAILED = 3,
+
+ // The user needs to satisfy a CAPTCHA challenge to unlock their account.
+ // If no other information is available, this can be resolved by visiting
+ // https://accounts.google.com/DisplayUnlockCaptcha. Otherwise, captcha()
+ // will provide details about the associated challenge.
+ CAPTCHA_REQUIRED = 4,
+
+ // The user account has been deleted.
+ ACCOUNT_DELETED = 5,
+
+ // The user account has been disabled.
+ ACCOUNT_DISABLED = 6,
+
+ // The service is not available; try again later.
+ SERVICE_UNAVAILABLE = 7,
+
+ // The password is valid but we need two factor to get a token.
+ TWO_FACTOR = 8,
+
+ // The requestor of the authentication step cancelled the request
+ // prior to completion.
+ REQUEST_CANCELED = 9,
+
+ // The user has provided a HOSTED account, when this service requires
+ // a GOOGLE account.
+ HOSTED_NOT_ALLOWED = 10,
+
+ // Indicates the service responded to a request, but we cannot
+ // interpret the response.
+ UNEXPECTED_SERVICE_RESPONSE = 11,
+
+ // Indicates the service responded and response carried details of the
+ // application error.
+ SERVICE_ERROR = 12,
+
+ // The number of known error states.
+ NUM_STATES = 13,
+ };
+
+ // Additional data for CAPTCHA_REQUIRED errors.
+ struct Captcha {
+ Captcha();
+ Captcha(const std::string& token,
+ const GURL& audio,
+ const GURL& img,
+ const GURL& unlock,
+ int width,
+ int height);
+ ~Captcha();
+ // For test only.
+ bool operator==(const Captcha &b) const;
+
+ std::string token; // Globally identifies the specific CAPTCHA challenge.
+ GURL audio_url; // The CAPTCHA audio to use instead of image.
+ GURL image_url; // The CAPTCHA image to show the user.
+ GURL unlock_url; // Pretty unlock page containing above captcha.
+ int image_width; // Width of captcha image.
+ int image_height; // Height of capture image.
+ };
+
+ // Additional data for TWO_FACTOR errors.
+ struct SecondFactor {
+ SecondFactor();
+ SecondFactor(const std::string& token,
+ const std::string& prompt,
+ const std::string& alternate,
+ int length);
+ ~SecondFactor();
+ // For test only.
+ bool operator==(const SecondFactor &b) const;
+
+ // Globally identifies the specific second-factor challenge.
+ std::string token;
+ // Localised prompt text, eg “Enter the verification code sent to your
+ // phone number ending in XXX”.
+ std::string prompt_text;
+ // Localized text describing an alternate option, eg “Get a verification
+ // code in a text message”.
+ std::string alternate_text;
+ // Character length for the challenge field.
+ int field_length;
+ };
+
+ // For test only.
+ bool operator==(const GoogleServiceAuthError &b) const;
+
+ // Construct a GoogleServiceAuthError from a State with no additional data.
+ explicit GoogleServiceAuthError(State s);
+
+ // Construct a GoogleServiceAuthError from a network error.
+ // It will be created with CONNECTION_FAILED set.
+ static GoogleServiceAuthError FromConnectionError(int error);
+
+ // Construct a CAPTCHA_REQUIRED error with CAPTCHA challenge data from the
+ // the ClientLogin endpoint.
+ // TODO(rogerta): once ClientLogin is no longer used, may be able to get
+ // rid of this function.
+ static GoogleServiceAuthError FromClientLoginCaptchaChallenge(
+ const std::string& captcha_token,
+ const GURL& captcha_image_url,
+ const GURL& captcha_unlock_url);
+
+ // Construct a SERVICE_ERROR error, e.g. invalid client ID, with an
+ // |error_message| which provides more information about the service error.
+ static GoogleServiceAuthError FromServiceError(
+ const std::string& error_message);
+
+ // Construct an UNEXPECTED_SERVICE_RESPONSE error, with an |error_message|
+ // detailing the problems with the response.
+ static GoogleServiceAuthError FromUnexpectedServiceResponse(
+ const std::string& error_message);
+
+ // Provided for convenience for clients needing to reset an instance to NONE.
+ // (avoids err_ = GoogleServiceAuthError(GoogleServiceAuthError::NONE), due
+ // to explicit class and State enum relation. Note: shouldn't be inlined!
+ static GoogleServiceAuthError AuthErrorNone();
+
+ // The error information.
+ State state() const;
+ const Captcha& captcha() const;
+ const SecondFactor& second_factor() const;
+ int network_error() const;
+ const std::string& token() const;
+ const std::string& error_message() const;
+
+ // Returns info about this object in a dictionary. Caller takes
+ // ownership of returned dictionary.
+ base::DictionaryValue* ToValue() const;
+
+ // Returns a message describing the error.
+ std::string ToString() const;
+
+ private:
+ GoogleServiceAuthError(State s, int error);
+
+ // Construct a GoogleServiceAuthError from |state| and |error_message|.
+ GoogleServiceAuthError(State state, const std::string& error_message);
+
+ explicit GoogleServiceAuthError(const std::string& error_message);
+
+ GoogleServiceAuthError(State s, const std::string& captcha_token,
+ const GURL& captcha_audio_url,
+ const GURL& captcha_image_url,
+ const GURL& captcha_unlock_url,
+ int image_width,
+ int image_height);
+
+ GoogleServiceAuthError(State s, const std::string& captcha_token,
+ const std::string& prompt_text,
+ const std::string& alternate_text,
+ int field_length);
+
+ State state_;
+ Captcha captcha_;
+ SecondFactor second_factor_;
+ int network_error_;
+ std::string error_message_;
+};
+
+#endif // GOOGLE_APIS_GAIA_GOOGLE_SERVICE_AUTH_ERROR_H_
diff --git a/chromium/google_apis/gaia/google_service_auth_error_unittest.cc b/chromium/google_apis/gaia/google_service_auth_error_unittest.cc
new file mode 100644
index 00000000000..d1f920ab9c2
--- /dev/null
+++ b/chromium/google_apis/gaia/google_service_auth_error_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/google_service_auth_error.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using base::ExpectDictStringValue;
+
+class GoogleServiceAuthErrorTest : public testing::Test {};
+
+void TestSimpleState(GoogleServiceAuthError::State state) {
+ GoogleServiceAuthError error(state);
+ scoped_ptr<DictionaryValue> value(error.ToValue());
+ EXPECT_EQ(1u, value->size());
+ std::string state_str;
+ EXPECT_TRUE(value->GetString("state", &state_str));
+ EXPECT_FALSE(state_str.empty());
+ EXPECT_NE("CONNECTION_FAILED", state_str);
+ EXPECT_NE("CAPTCHA_REQUIRED", state_str);
+}
+
+TEST_F(GoogleServiceAuthErrorTest, SimpleToValue) {
+ for (int i = GoogleServiceAuthError::NONE;
+ i <= GoogleServiceAuthError::USER_NOT_SIGNED_UP; ++i) {
+ TestSimpleState(static_cast<GoogleServiceAuthError::State>(i));
+ }
+}
+
+TEST_F(GoogleServiceAuthErrorTest, None) {
+ GoogleServiceAuthError error(GoogleServiceAuthError::AuthErrorNone());
+ scoped_ptr<DictionaryValue> value(error.ToValue());
+ EXPECT_EQ(1u, value->size());
+ ExpectDictStringValue("NONE", *value, "state");
+}
+
+TEST_F(GoogleServiceAuthErrorTest, ConnectionFailed) {
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::FromConnectionError(net::OK));
+ scoped_ptr<DictionaryValue> value(error.ToValue());
+ EXPECT_EQ(2u, value->size());
+ ExpectDictStringValue("CONNECTION_FAILED", *value, "state");
+ ExpectDictStringValue("net::OK", *value, "networkError");
+}
+
+} // namespace
diff --git a/chromium/google_apis/gaia/mock_url_fetcher_factory.h b/chromium/google_apis/gaia/mock_url_fetcher_factory.h
new file mode 100644
index 00000000000..862f220e380
--- /dev/null
+++ b/chromium/google_apis/gaia/mock_url_fetcher_factory.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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.
+//
+// A collection of classes that are useful when testing things that use a
+// GaiaAuthFetcher.
+
+#ifndef GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
+#define GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
+
+#include <string>
+
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+
+// Responds as though ClientLogin returned from the server.
+class MockFetcher : public net::TestURLFetcher {
+ public:
+ MockFetcher(bool success,
+ const GURL& url,
+ const std::string& results,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d);
+
+ MockFetcher(const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const net::ResponseCookies& cookies,
+ const std::string& results,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d);
+
+ virtual ~MockFetcher();
+
+ virtual void Start() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockFetcher);
+};
+
+template<typename T>
+class MockURLFetcherFactory : public net::URLFetcherFactory,
+ public net::ScopedURLFetcherFactory {
+ public:
+ MockURLFetcherFactory()
+ : net::ScopedURLFetcherFactory(this),
+ success_(true) {
+ }
+ ~MockURLFetcherFactory() {}
+ net::URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ net::URLFetcher::RequestType request_type,
+ net::URLFetcherDelegate* d) OVERRIDE {
+ return new T(success_, url, results_, request_type, d);
+ }
+ void set_success(bool success) {
+ success_ = success;
+ }
+ void set_results(const std::string& results) {
+ results_ = results;
+ }
+ private:
+ bool success_;
+ std::string results_;
+ DISALLOW_COPY_AND_ASSIGN(MockURLFetcherFactory);
+};
+
+#endif // GOOGLE_APIS_GAIA_MOCK_URL_FETCHER_FACTORY_H_
diff --git a/chromium/google_apis/gaia/oauth2_access_token_consumer.h b/chromium/google_apis/gaia/oauth2_access_token_consumer.h
new file mode 100644
index 00000000000..65d32e85ab1
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_consumer.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
+
+#include <string>
+
+class GoogleServiceAuthError;
+
+namespace base {
+class Time;
+}
+
+// An interface that defines the callbacks for consumers to which
+// OAuth2AccessTokenFetcher can return results.
+class OAuth2AccessTokenConsumer {
+ public:
+ // Success callback. |access_token| will contain a valid OAuth2 access token.
+ // |expiration_time| is the date until which the token can be used. This
+ // value has a built-in safety margin, so it can be used as-is.
+ virtual void OnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time) {}
+ virtual void OnGetTokenFailure(const GoogleServiceAuthError& error) {}
+
+ protected:
+ virtual ~OAuth2AccessTokenConsumer() {}
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_CONSUMER_H_
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc
new file mode 100644
index 00000000000..456251f0134
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using net::ResponseCookies;
+using net::URLFetcher;
+using net::URLFetcherDelegate;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+namespace {
+static const char kGetAccessTokenBodyFormat[] =
+ "client_id=%s&"
+ "client_secret=%s&"
+ "grant_type=refresh_token&"
+ "refresh_token=%s";
+
+static const char kGetAccessTokenBodyWithScopeFormat[] =
+ "client_id=%s&"
+ "client_secret=%s&"
+ "grant_type=refresh_token&"
+ "refresh_token=%s&"
+ "scope=%s";
+
+static const char kAccessTokenKey[] = "access_token";
+static const char kExpiresInKey[] = "expires_in";
+
+static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
+ CHECK(!status.is_success());
+ if (status.status() == URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ } else {
+ DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
+ << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+}
+
+static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
+ const GURL& url,
+ const std::string& body,
+ URLFetcherDelegate* delegate) {
+ bool empty_body = body.empty();
+ URLFetcher* result = net::URLFetcher::Create(
+ 0, url,
+ empty_body ? URLFetcher::GET : URLFetcher::POST,
+ delegate);
+
+ result->SetRequestContext(getter);
+ result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ // Fetchers are sometimes cancelled because a network change was detected,
+ // especially at startup and after sign-in on ChromeOS. Retrying once should
+ // be enough in those cases; let the fetcher retry up to 3 times just in case.
+ // http://crbug.com/163710
+ result->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ if (!empty_body)
+ result->SetUploadData("application/x-www-form-urlencoded", body);
+
+ return result;
+}
+} // namespace
+
+OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
+ OAuth2AccessTokenConsumer* consumer,
+ URLRequestContextGetter* getter)
+ : consumer_(consumer),
+ getter_(getter),
+ state_(INITIAL) { }
+
+OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
+
+void OAuth2AccessTokenFetcher::CancelRequest() {
+ fetcher_.reset();
+}
+
+void OAuth2AccessTokenFetcher::Start(const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes) {
+ client_id_ = client_id;
+ client_secret_ = client_secret;
+ refresh_token_ = refresh_token;
+ scopes_ = scopes;
+ StartGetAccessToken();
+}
+
+void OAuth2AccessTokenFetcher::StartGetAccessToken() {
+ CHECK_EQ(INITIAL, state_);
+ state_ = GET_ACCESS_TOKEN_STARTED;
+ fetcher_.reset(CreateFetcher(
+ getter_,
+ MakeGetAccessTokenUrl(),
+ MakeGetAccessTokenBody(
+ client_id_, client_secret_, refresh_token_, scopes_),
+ this));
+ fetcher_->Start(); // OnURLFetchComplete will be called.
+}
+
+void OAuth2AccessTokenFetcher::EndGetAccessToken(
+ const net::URLFetcher* source) {
+ CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
+ state_ = GET_ACCESS_TOKEN_DONE;
+
+ URLRequestStatus status = source->GetStatus();
+ if (!status.is_success()) {
+ OnGetTokenFailure(CreateAuthError(status));
+ return;
+ }
+
+ // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
+ // '403 Rate Limit Exeeded.'
+ if (source->GetResponseCode() == net::HTTP_FORBIDDEN) {
+ OnGetTokenFailure(GoogleServiceAuthError(
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE));
+ return;
+ }
+
+ // The other errors are treated as permanent error.
+ if (source->GetResponseCode() != net::HTTP_OK) {
+ OnGetTokenFailure(GoogleServiceAuthError(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
+ return;
+ }
+
+ // The request was successfully fetched and it returned OK.
+ // Parse out the access token and the expiration time.
+ std::string access_token;
+ int expires_in;
+ if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) {
+ DLOG(WARNING) << "Response doesn't match expected format";
+ OnGetTokenFailure(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
+ return;
+ }
+ // The token will expire in |expires_in| seconds. Take a 10% error margin to
+ // prevent reusing a token too close to its expiration date.
+ OnGetTokenSuccess(
+ access_token,
+ base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
+}
+
+void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ consumer_->OnGetTokenSuccess(access_token, expiration_time);
+}
+
+void OAuth2AccessTokenFetcher::OnGetTokenFailure(
+ const GoogleServiceAuthError& error) {
+ state_ = ERROR_STATE;
+ consumer_->OnGetTokenFailure(error);
+}
+
+void OAuth2AccessTokenFetcher::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ CHECK(source);
+ CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
+ EndGetAccessToken(source);
+}
+
+// static
+GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
+ return GURL(GaiaUrls::GetInstance()->oauth2_token_url());
+}
+
+// static
+std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes) {
+ std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
+ std::string enc_client_secret =
+ net::EscapeUrlEncodedData(client_secret, true);
+ std::string enc_refresh_token =
+ net::EscapeUrlEncodedData(refresh_token, true);
+ if (scopes.empty()) {
+ return base::StringPrintf(
+ kGetAccessTokenBodyFormat,
+ enc_client_id.c_str(),
+ enc_client_secret.c_str(),
+ enc_refresh_token.c_str());
+ } else {
+ std::string scopes_string = JoinString(scopes, ' ');
+ return base::StringPrintf(
+ kGetAccessTokenBodyWithScopeFormat,
+ enc_client_id.c_str(),
+ enc_client_secret.c_str(),
+ enc_refresh_token.c_str(),
+ net::EscapeUrlEncodedData(scopes_string, true).c_str());
+ }
+}
+
+// static
+bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ const net::URLFetcher* source,
+ std::string* access_token,
+ int* expires_in) {
+ CHECK(source);
+ CHECK(access_token);
+ std::string data;
+ source->GetResponseAsString(&data);
+ scoped_ptr<base::Value> value(base::JSONReader::Read(data));
+ if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
+ return false;
+
+ base::DictionaryValue* dict =
+ static_cast<base::DictionaryValue*>(value.get());
+ return dict->GetString(kAccessTokenKey, access_token) &&
+ dict->GetInteger(kExpiresInKey, expires_in);
+}
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher.h b/chromium/google_apis/gaia/oauth2_access_token_fetcher.h
new file mode 100644
index 00000000000..11ac6ea8b5c
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "url/gurl.h"
+
+class OAuth2AccessTokenFetcherTest;
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+class URLRequestStatus;
+}
+
+// Abstracts the details to get OAuth2 access token token from
+// OAuth2 refresh token.
+// See "Using the Refresh Token" section in:
+// http://code.google.com/apis/accounts/docs/OAuth2WebServer.html
+//
+// This class should be used on a single thread, but it can be whichever thread
+// that you like.
+// Also, do not reuse the same instance. Once Start() is called, the instance
+// should not be reused.
+//
+// Usage:
+// * Create an instance with a consumer.
+// * Call Start()
+// * The consumer passed in the constructor will be called on the same
+// thread Start was called with the results.
+//
+// This class can handle one request at a time. To parallelize requests,
+// create multiple instances.
+class OAuth2AccessTokenFetcher : public net::URLFetcherDelegate {
+ public:
+ OAuth2AccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter);
+ virtual ~OAuth2AccessTokenFetcher();
+
+ // Starts the flow with the given parameters.
+ // |scopes| can be empty. If it is empty then the access token will have the
+ // same scope as the refresh token. If not empty, then access token will have
+ // the scopes specified. In this case, the access token will successfully be
+ // generated only if refresh token has login scope of a list of scopes that is
+ // a super-set of the specified scopes.
+ virtual void Start(const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes);
+
+ void CancelRequest();
+
+ // Implementation of net::URLFetcherDelegate
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ private:
+ enum State {
+ INITIAL,
+ GET_ACCESS_TOKEN_STARTED,
+ GET_ACCESS_TOKEN_DONE,
+ ERROR_STATE,
+ };
+
+ // Helper methods for the flow.
+ void StartGetAccessToken();
+ void EndGetAccessToken(const net::URLFetcher* source);
+
+ // Helper mehtods for reporting back results.
+ void OnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time);
+ void OnGetTokenFailure(const GoogleServiceAuthError& error);
+
+ // Other helpers.
+ static GURL MakeGetAccessTokenUrl();
+ static std::string MakeGetAccessTokenBody(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes);
+ static bool ParseGetAccessTokenResponse(const net::URLFetcher* source,
+ std::string* access_token,
+ int* expires_in);
+
+ // State that is set during construction.
+ OAuth2AccessTokenConsumer* const consumer_;
+ net::URLRequestContextGetter* const getter_;
+ State state_;
+
+ // While a fetch is in progress.
+ scoped_ptr<net::URLFetcher> fetcher_;
+ std::string client_id_;
+ std::string client_secret_;
+ std::string refresh_token_;
+ std::vector<std::string> scopes_;
+
+ friend class OAuth2AccessTokenFetcherTest;
+ FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherTest,
+ ParseGetAccessTokenResponse);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2AccessTokenFetcherTest,
+ MakeGetAccessTokenBody);
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2AccessTokenFetcher);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_H_
diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc
new file mode 100644
index 00000000000..6b12da3fbd0
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 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.
+//
+// A complete set of unit tests for OAuth2AccessTokenFetcher.
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using net::ResponseCookies;
+using net::ScopedURLFetcherFactory;
+using net::TestURLFetcher;
+using net::URLFetcher;
+using net::URLFetcherDelegate;
+using net::URLFetcherFactory;
+using net::URLRequestStatus;
+using testing::_;
+using testing::Return;
+
+namespace {
+
+typedef std::vector<std::string> ScopeList;
+
+static const char kValidTokenResponse[] =
+ "{"
+ " \"access_token\": \"at1\","
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+static const char kTokenResponseNoAccessToken[] =
+ "{"
+ " \"expires_in\": 3600,"
+ " \"token_type\": \"Bearer\""
+ "}";
+
+class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
+ public URLFetcherFactory {
+public:
+ MockUrlFetcherFactory()
+ : ScopedURLFetcherFactory(this) {
+ }
+ virtual ~MockUrlFetcherFactory() {}
+
+ MOCK_METHOD4(
+ CreateURLFetcher,
+ URLFetcher* (int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d));
+};
+
+class MockOAuth2AccessTokenConsumer : public OAuth2AccessTokenConsumer {
+ public:
+ MockOAuth2AccessTokenConsumer() {}
+ ~MockOAuth2AccessTokenConsumer() {}
+
+ MOCK_METHOD2(OnGetTokenSuccess, void(const std::string& access_token,
+ const base::Time& expiration_time));
+ MOCK_METHOD1(OnGetTokenFailure,
+ void(const GoogleServiceAuthError& error));
+};
+
+} // namespace
+
+class OAuth2AccessTokenFetcherTest : public testing::Test {
+ public:
+ OAuth2AccessTokenFetcherTest()
+ : request_context_getter_(new net::TestURLRequestContextGetter(
+ base::MessageLoopProxy::current())),
+ fetcher_(&consumer_, request_context_getter_) {
+ }
+
+ virtual ~OAuth2AccessTokenFetcherTest() {}
+
+ virtual TestURLFetcher* SetupGetAccessToken(
+ bool fetch_succeeds, int response_code, const std::string& body) {
+ GURL url(GaiaUrls::GetInstance()->oauth2_token_url());
+ TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, &fetcher_);
+ URLRequestStatus::Status status =
+ fetch_succeeds ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED;
+ url_fetcher->set_status(URLRequestStatus(status, 0));
+
+ if (response_code != 0)
+ url_fetcher->set_response_code(response_code);
+
+ if (!body.empty())
+ url_fetcher->SetResponseString(body);
+
+ EXPECT_CALL(factory_, CreateURLFetcher(_, url, _, _))
+ .WillOnce(Return(url_fetcher));
+ return url_fetcher;
+ }
+
+ protected:
+ content::TestBrowserThreadBundle thread_bundle_;
+ MockUrlFetcherFactory factory_;
+ MockOAuth2AccessTokenConsumer consumer_;
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
+ OAuth2AccessTokenFetcher fetcher_;
+};
+
+// These four tests time out, see http://crbug.com/113446.
+TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_GetAccessTokenRequestFailure) {
+ TestURLFetcher* url_fetcher = SetupGetAccessToken(false, 0, std::string());
+ EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
+ fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest,
+ DISABLED_GetAccessTokenResponseCodeFailure) {
+ TestURLFetcher* url_fetcher =
+ SetupGetAccessToken(true, net::HTTP_FORBIDDEN, std::string());
+ EXPECT_CALL(consumer_, OnGetTokenFailure(_)).Times(1);
+ fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_Success) {
+ TestURLFetcher* url_fetcher = SetupGetAccessToken(
+ true, net::HTTP_OK, kValidTokenResponse);
+ EXPECT_CALL(consumer_, OnGetTokenSuccess("at1", _)).Times(1);
+ fetcher_.Start("client_id", "client_secret", "refresh_token", ScopeList());
+ fetcher_.OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2AccessTokenFetcherTest, DISABLED_MakeGetAccessTokenBody) {
+ { // No scope.
+ std::string body =
+ "client_id=cid1&"
+ "client_secret=cs1&"
+ "grant_type=refresh_token&"
+ "refresh_token=rt1";
+ EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", ScopeList()));
+ }
+
+ { // One scope.
+ std::string body =
+ "client_id=cid1&"
+ "client_secret=cs1&"
+ "grant_type=refresh_token&"
+ "refresh_token=rt1&"
+ "scope=https://www.googleapis.com/foo";
+ ScopeList scopes;
+ scopes.push_back("https://www.googleapis.com/foo");
+ EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", scopes));
+ }
+
+ { // Multiple scopes.
+ std::string body =
+ "client_id=cid1&"
+ "client_secret=cs1&"
+ "grant_type=refresh_token&"
+ "refresh_token=rt1&"
+ "scope=https://www.googleapis.com/foo+"
+ "https://www.googleapis.com/bar+"
+ "https://www.googleapis.com/baz";
+ ScopeList scopes;
+ scopes.push_back("https://www.googleapis.com/foo");
+ scopes.push_back("https://www.googleapis.com/bar");
+ scopes.push_back("https://www.googleapis.com/baz");
+ EXPECT_EQ(body, OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
+ "cid1", "cs1", "rt1", scopes));
+ }
+}
+
+// http://crbug.com/114215
+#if defined(OS_WIN)
+#define MAYBE_ParseGetAccessTokenResponse DISABLED_ParseGetAccessTokenResponse
+#else
+#define MAYBE_ParseGetAccessTokenResponse ParseGetAccessTokenResponse
+#endif // defined(OS_WIN)
+TEST_F(OAuth2AccessTokenFetcherTest, MAYBE_ParseGetAccessTokenResponse) {
+ { // No body.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+
+ std::string at;
+ int expires_in;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at, &expires_in));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Bad json.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString("foo");
+
+ std::string at;
+ int expires_in;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at, &expires_in));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Valid json: access token missing.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
+
+ std::string at;
+ int expires_in;
+ EXPECT_FALSE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at, &expires_in));
+ EXPECT_TRUE(at.empty());
+ }
+ { // Valid json: all good.
+ TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
+ url_fetcher.SetResponseString(kValidTokenResponse);
+
+ std::string at;
+ int expires_in;
+ EXPECT_TRUE(OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
+ &url_fetcher, &at, &expires_in));
+ EXPECT_EQ("at1", at);
+ EXPECT_EQ(3600, expires_in);
+ }
+}
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow.cc b/chromium/google_apis/gaia/oauth2_api_call_flow.cc
new file mode 100644
index 00000000000..4c4940d9853
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/oauth2_api_call_flow.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/strings/stringprintf.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using net::ResponseCookies;
+using net::URLFetcher;
+using net::URLFetcherDelegate;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+namespace {
+static const char kAuthorizationHeaderFormat[] =
+ "Authorization: Bearer %s";
+
+static std::string MakeAuthorizationHeader(const std::string& auth_token) {
+ return base::StringPrintf(kAuthorizationHeaderFormat, auth_token.c_str());
+}
+} // namespace
+
+OAuth2ApiCallFlow::OAuth2ApiCallFlow(
+ net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes)
+ : context_(context),
+ refresh_token_(refresh_token),
+ access_token_(access_token),
+ scopes_(scopes),
+ state_(INITIAL),
+ tried_mint_access_token_(false) {
+}
+
+OAuth2ApiCallFlow::~OAuth2ApiCallFlow() {}
+
+void OAuth2ApiCallFlow::Start() {
+ BeginApiCall();
+}
+
+void OAuth2ApiCallFlow::BeginApiCall() {
+ CHECK(state_ == INITIAL || state_ == MINT_ACCESS_TOKEN_DONE);
+
+ // If the access token is empty then directly try to mint one.
+ if (access_token_.empty()) {
+ BeginMintAccessToken();
+ } else {
+ state_ = API_CALL_STARTED;
+ url_fetcher_.reset(CreateURLFetcher());
+ url_fetcher_->Start(); // OnURLFetchComplete will be called.
+ }
+}
+
+void OAuth2ApiCallFlow::EndApiCall(const net::URLFetcher* source) {
+ CHECK_EQ(API_CALL_STARTED, state_);
+ state_ = API_CALL_DONE;
+
+ URLRequestStatus status = source->GetStatus();
+ if (!status.is_success()) {
+ state_ = ERROR_STATE;
+ ProcessApiCallFailure(source);
+ return;
+ }
+
+ // If the response code is 401 Unauthorized then access token may have
+ // expired. So try generating a new access token.
+ if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
+ // If we already tried minting a new access token, don't do it again.
+ if (tried_mint_access_token_) {
+ state_ = ERROR_STATE;
+ ProcessApiCallFailure(source);
+ } else {
+ BeginMintAccessToken();
+ }
+
+ return;
+ }
+
+ if (source->GetResponseCode() != net::HTTP_OK) {
+ state_ = ERROR_STATE;
+ ProcessApiCallFailure(source);
+ return;
+ }
+
+ ProcessApiCallSuccess(source);
+}
+
+void OAuth2ApiCallFlow::BeginMintAccessToken() {
+ CHECK(state_ == INITIAL || state_ == API_CALL_DONE);
+ CHECK(!tried_mint_access_token_);
+ state_ = MINT_ACCESS_TOKEN_STARTED;
+ tried_mint_access_token_ = true;
+
+ oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher());
+ oauth2_access_token_fetcher_->Start(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ refresh_token_,
+ scopes_);
+}
+
+void OAuth2ApiCallFlow::EndMintAccessToken(
+ const GoogleServiceAuthError* error) {
+ CHECK_EQ(MINT_ACCESS_TOKEN_STARTED, state_);
+
+ if (!error) {
+ state_ = MINT_ACCESS_TOKEN_DONE;
+ BeginApiCall();
+ } else {
+ state_ = ERROR_STATE;
+ ProcessMintAccessTokenFailure(*error);
+ }
+}
+
+OAuth2AccessTokenFetcher* OAuth2ApiCallFlow::CreateAccessTokenFetcher() {
+ return new OAuth2AccessTokenFetcher(this, context_);
+}
+
+void OAuth2ApiCallFlow::OnURLFetchComplete(const net::URLFetcher* source) {
+ CHECK(source);
+ CHECK_EQ(API_CALL_STARTED, state_);
+ EndApiCall(source);
+}
+
+void OAuth2ApiCallFlow::OnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time) {
+ access_token_ = access_token;
+ EndMintAccessToken(NULL);
+}
+
+void OAuth2ApiCallFlow::OnGetTokenFailure(
+ const GoogleServiceAuthError& error) {
+ EndMintAccessToken(&error);
+}
+
+URLFetcher* OAuth2ApiCallFlow::CreateURLFetcher() {
+ std::string body = CreateApiCallBody();
+ bool empty_body = body.empty();
+ URLFetcher* result = net::URLFetcher::Create(
+ 0,
+ CreateApiCallUrl(),
+ empty_body ? URLFetcher::GET : URLFetcher::POST,
+ this);
+
+ result->SetRequestContext(context_);
+ result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ result->AddExtraRequestHeader(MakeAuthorizationHeader(access_token_));
+ // Fetchers are sometimes cancelled because a network change was detected,
+ // especially at startup and after sign-in on ChromeOS. Retrying once should
+ // be enough in those cases; let the fetcher retry up to 3 times just in case.
+ // http://crbug.com/163710
+ result->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ if (!empty_body)
+ result->SetUploadData("application/x-www-form-urlencoded", body);
+
+ return result;
+}
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow.h b/chromium/google_apis/gaia/oauth2_api_call_flow.h
new file mode 100644
index 00000000000..fccd1e753be
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_API_CALL_FLOW_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_API_CALL_FLOW_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+
+class GoogleServiceAuthError;
+class OAuth2MintTokenFlowTest;
+
+namespace net {
+class URLFetcher;
+class URLRequestContextGetter;
+}
+
+// Base class for all classes that implement a flow to call OAuth2
+// enabled APIs.
+//
+// Given a refresh token, an access token, and a list of scopes an OAuth2
+// enabled API is called in the following way:
+// 1. Try the given access token to call the API.
+// 2. If that does not work, use the refresh token and scopes to generate
+// a new access token.
+// 3. Try the new access token to call the API.
+//
+// This class abstracts the basic steps and exposes template methods
+// for sub-classes to implement for API specific details.
+class OAuth2ApiCallFlow
+ : public net::URLFetcherDelegate,
+ public OAuth2AccessTokenConsumer {
+ public:
+ // Creates an instance that works with the given data.
+ // Note that |access_token| can be empty. In that case, the flow will skip
+ // the first step (of trying an existing access token).
+ OAuth2ApiCallFlow(
+ net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes);
+
+ virtual ~OAuth2ApiCallFlow();
+
+ // Start the flow.
+ virtual void Start();
+
+ // OAuth2AccessTokenFetcher implementation.
+ virtual void OnGetTokenSuccess(const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const GoogleServiceAuthError& error) OVERRIDE;
+
+ // net::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ protected:
+ // Template methods for sub-classes.
+
+ // Methods to help create HTTP request.
+ virtual GURL CreateApiCallUrl() = 0;
+ virtual std::string CreateApiCallBody() = 0;
+
+ // Sub-classes can expose an appropriate observer interface by implementing
+ // these template methods.
+ // Called when the API call finished successfully.
+ virtual void ProcessApiCallSuccess(const net::URLFetcher* source) = 0;
+ // Called when the API call failed.
+ virtual void ProcessApiCallFailure(const net::URLFetcher* source) = 0;
+ // Called when a new access token is generated.
+ virtual void ProcessNewAccessToken(const std::string& access_token) = 0;
+ virtual void ProcessMintAccessTokenFailure(
+ const GoogleServiceAuthError& error) = 0;
+
+ private:
+ enum State {
+ INITIAL,
+ API_CALL_STARTED,
+ API_CALL_DONE,
+ MINT_ACCESS_TOKEN_STARTED,
+ MINT_ACCESS_TOKEN_DONE,
+ ERROR_STATE
+ };
+
+ friend class OAuth2ApiCallFlowTest;
+ FRIEND_TEST_ALL_PREFIXES(OAuth2ApiCallFlowTest, CreateURLFetcher);
+
+ // Helper to create an instance of access token fetcher.
+ // Caller owns the returned instance.
+ // Note that this is virtual since it is mocked during unit testing.
+ virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher();
+
+ // Creates an instance of URLFetcher that does not send or save cookies.
+ // Template method CreateApiCallUrl is used to get the URL.
+ // Template method CreateApiCallBody is used to get the body.
+ // The URLFether's method will be GET if body is empty, POST otherwise.
+ // Caller owns the returned instance.
+ // Note that this is virtual since it is mocked during unit testing.
+ virtual net::URLFetcher* CreateURLFetcher();
+
+ // Helper methods to implement the state machine for the flow.
+ void BeginApiCall();
+ void EndApiCall(const net::URLFetcher* source);
+ void BeginMintAccessToken();
+ void EndMintAccessToken(const GoogleServiceAuthError* error);
+
+ net::URLRequestContextGetter* context_;
+ std::string refresh_token_;
+ std::string access_token_;
+ std::vector<std::string> scopes_;
+
+ State state_;
+ // Whether we have already tried minting an access token once.
+ bool tried_mint_access_token_;
+
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ scoped_ptr<OAuth2AccessTokenFetcher> oauth2_access_token_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2ApiCallFlow);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_API_CALL_FLOW_H_
diff --git a/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc b/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc
new file mode 100644
index 00000000000..d56a613eded
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_api_call_flow_unittest.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2012 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.
+//
+// A complete set of unit tests for OAuth2MintTokenFlow.
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "google_apis/gaia/oauth2_api_call_flow.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::HttpRequestHeaders;
+using net::ScopedURLFetcherFactory;
+using net::TestURLFetcher;
+using net::URLFetcher;
+using net::URLFetcherDelegate;
+using net::URLFetcherFactory;
+using net::URLRequestStatus;
+using testing::_;
+using testing::Return;
+
+namespace {
+
+static std::string CreateBody() {
+ return "some body";
+}
+
+static GURL CreateApiUrl() {
+ return GURL("https://www.googleapis.com/someapi");
+}
+
+static std::vector<std::string> CreateTestScopes() {
+ std::vector<std::string> scopes;
+ scopes.push_back("scope1");
+ scopes.push_back("scope2");
+ return scopes;
+}
+
+class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
+ public URLFetcherFactory {
+ public:
+ MockUrlFetcherFactory()
+ : ScopedURLFetcherFactory(this) {
+ }
+ virtual ~MockUrlFetcherFactory() {}
+
+ MOCK_METHOD4(
+ CreateURLFetcher,
+ URLFetcher* (int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d));
+};
+
+class MockAccessTokenFetcher : public OAuth2AccessTokenFetcher {
+ public:
+ MockAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter)
+ : OAuth2AccessTokenFetcher(consumer, getter) {}
+ ~MockAccessTokenFetcher() {}
+
+ MOCK_METHOD4(Start,
+ void (const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes));
+};
+
+class MockApiCallFlow : public OAuth2ApiCallFlow {
+ public:
+ MockApiCallFlow(net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes)
+ : OAuth2ApiCallFlow(context, refresh_token, access_token, scopes) {}
+ ~MockApiCallFlow() {}
+
+ MOCK_METHOD0(CreateApiCallUrl, GURL ());
+ MOCK_METHOD0(CreateApiCallBody, std::string ());
+ MOCK_METHOD1(ProcessApiCallSuccess,
+ void (const URLFetcher* source));
+ MOCK_METHOD1(ProcessApiCallFailure,
+ void (const URLFetcher* source));
+ MOCK_METHOD1(ProcessNewAccessToken,
+ void (const std::string& access_token));
+ MOCK_METHOD1(ProcessMintAccessTokenFailure,
+ void (const GoogleServiceAuthError& error));
+ MOCK_METHOD0(CreateAccessTokenFetcher, OAuth2AccessTokenFetcher* ());
+};
+
+} // namespace
+
+class OAuth2ApiCallFlowTest : public testing::Test {
+ protected:
+ void SetupAccessTokenFetcher(
+ const std::string& rt, const std::vector<std::string>& scopes) {
+ EXPECT_CALL(*access_token_fetcher_,
+ Start(GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ rt, scopes))
+ .Times(1);
+ EXPECT_CALL(*flow_, CreateAccessTokenFetcher())
+ .WillOnce(Return(access_token_fetcher_.release()));
+ }
+
+ TestURLFetcher* CreateURLFetcher(
+ const GURL& url, bool fetch_succeeds,
+ int response_code, const std::string& body) {
+ TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, flow_.get());
+ URLRequestStatus::Status status =
+ fetch_succeeds ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED;
+ url_fetcher->set_status(URLRequestStatus(status, 0));
+
+ if (response_code != 0)
+ url_fetcher->set_response_code(response_code);
+
+ if (!body.empty())
+ url_fetcher->SetResponseString(body);
+
+ return url_fetcher;
+ }
+
+ void CreateFlow(const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes) {
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
+ new net::TestURLRequestContextGetter(
+ message_loop_.message_loop_proxy());
+ flow_.reset(new MockApiCallFlow(
+ request_context_getter, refresh_token, access_token, scopes));
+ access_token_fetcher_.reset(
+ new MockAccessTokenFetcher(flow_.get(), request_context_getter));
+ }
+
+ TestURLFetcher* SetupApiCall(bool succeeds, net::HttpStatusCode status) {
+ std::string body(CreateBody());
+ GURL url(CreateApiUrl());
+ EXPECT_CALL(*flow_, CreateApiCallBody()).WillOnce(Return(body));
+ EXPECT_CALL(*flow_, CreateApiCallUrl()).WillOnce(Return(url));
+ TestURLFetcher* url_fetcher =
+ CreateURLFetcher(url, succeeds, status, std::string());
+ EXPECT_CALL(factory_, CreateURLFetcher(_, url, _, _))
+ .WillOnce(Return(url_fetcher));
+ return url_fetcher;
+ }
+
+ MockUrlFetcherFactory factory_;
+ scoped_ptr<MockApiCallFlow> flow_;
+ scoped_ptr<MockAccessTokenFetcher> access_token_fetcher_;
+ base::MessageLoop message_loop_;
+};
+
+TEST_F(OAuth2ApiCallFlowTest, FirstApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher));
+ flow_->Start();
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, SecondApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher1);
+ TestURLFetcher* url_fetcher2 = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher2));
+ flow_->OnGetTokenSuccess(
+ at,
+ base::Time::Now() + base::TimeDelta::FromMinutes(3600));
+ flow_->OnURLFetchComplete(url_fetcher2);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, SecondApiCallFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher1);
+ TestURLFetcher* url_fetcher2 = SetupApiCall(false, net::HTTP_UNAUTHORIZED);
+ EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher2));
+ flow_->OnGetTokenSuccess(
+ at,
+ base::Time::Now() + base::TimeDelta::FromMinutes(3600));
+ flow_->OnURLFetchComplete(url_fetcher2);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, NewTokenGenerationFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher);
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ EXPECT_CALL(*flow_, ProcessMintAccessTokenFailure(error));
+ flow_->OnGetTokenFailure(error);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenFirstApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, std::string(), scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher));
+ flow_->Start();
+ flow_->OnGetTokenSuccess(
+ at,
+ base::Time::Now() + base::TimeDelta::FromMinutes(3600));
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenApiCallFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, std::string(), scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(false, net::HTTP_BAD_GATEWAY);
+ EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher));
+ flow_->Start();
+ flow_->OnGetTokenSuccess(
+ at,
+ base::Time::Now() + base::TimeDelta::FromMinutes(3600));
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenNewTokenGenerationFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, std::string(), scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ EXPECT_CALL(*flow_, ProcessMintAccessTokenFailure(error));
+ flow_->Start();
+ flow_->OnGetTokenFailure(error);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, CreateURLFetcher) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+ std::string body = CreateBody();
+ GURL url(CreateApiUrl());
+
+ CreateFlow(rt, at, scopes);
+ scoped_ptr<TestURLFetcher> url_fetcher(SetupApiCall(true, net::HTTP_OK));
+ flow_->CreateURLFetcher();
+ HttpRequestHeaders headers;
+ url_fetcher->GetExtraRequestHeaders(&headers);
+ std::string auth_header;
+ EXPECT_TRUE(headers.GetHeader("Authorization", &auth_header));
+ EXPECT_EQ("Bearer access_token", auth_header);
+ EXPECT_EQ(url, url_fetcher->GetOriginalURL());
+ EXPECT_EQ(body, url_fetcher->upload_data());
+}
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow.cc b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc
new file mode 100644
index 00000000000..f66e0fb11d2
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/oauth2_mint_token_flow.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/json/json_reader.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using net::URLFetcher;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+namespace {
+
+static const char kForceValueFalse[] = "false";
+static const char kForceValueTrue[] = "true";
+static const char kResponseTypeValueNone[] = "none";
+static const char kResponseTypeValueToken[] = "token";
+
+static const char kOAuth2IssueTokenBodyFormat[] =
+ "force=%s"
+ "&response_type=%s"
+ "&scope=%s"
+ "&client_id=%s"
+ "&origin=%s";
+static const char kIssueAdviceKey[] = "issueAdvice";
+static const char kIssueAdviceValueAuto[] = "auto";
+static const char kIssueAdviceValueConsent[] = "consent";
+static const char kAccessTokenKey[] = "token";
+static const char kConsentKey[] = "consent";
+static const char kExpiresInKey[] = "expiresIn";
+static const char kScopesKey[] = "scopes";
+static const char kDescriptionKey[] = "description";
+static const char kDetailKey[] = "detail";
+static const char kDetailSeparators[] = "\n";
+static const char kError[] = "error";
+static const char kMessage[] = "message";
+
+static GoogleServiceAuthError CreateAuthError(const net::URLFetcher* source) {
+ URLRequestStatus status = source->GetStatus();
+ if (status.status() == URLRequestStatus::CANCELED) {
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
+ }
+ if (status.status() == URLRequestStatus::FAILED) {
+ DLOG(WARNING) << "Server returned error: errno " << status.error();
+ return GoogleServiceAuthError::FromConnectionError(status.error());
+ }
+
+ std::string response_body;
+ source->GetResponseAsString(&response_body);
+ scoped_ptr<Value> value(base::JSONReader::Read(response_body));
+ DictionaryValue* response;
+ if (!value.get() || !value->GetAsDictionary(&response)) {
+ return GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ base::StringPrintf(
+ "Not able to parse a JSON object from a service response. "
+ "HTTP Status of the response is: %d", source->GetResponseCode()));
+ }
+ DictionaryValue* error;
+ if (!response->GetDictionary(kError, &error)) {
+ return GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to find a detailed error in a service response.");
+ }
+ std::string message;
+ if (!error->GetString(kMessage, &message)) {
+ return GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to find an error message within a service error.");
+ }
+ return GoogleServiceAuthError::FromServiceError(message);
+}
+
+} // namespace
+
+IssueAdviceInfoEntry::IssueAdviceInfoEntry() {}
+IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {}
+
+bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const {
+ return description == rhs.description && details == rhs.details;
+}
+
+OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
+
+OAuth2MintTokenFlow::Parameters::Parameters(
+ const std::string& at,
+ const std::string& eid,
+ const std::string& cid,
+ const std::vector<std::string>& scopes_arg,
+ Mode mode_arg)
+ : access_token(at),
+ extension_id(eid),
+ client_id(cid),
+ scopes(scopes_arg),
+ mode(mode_arg) {
+}
+
+OAuth2MintTokenFlow::Parameters::~Parameters() {}
+
+OAuth2MintTokenFlow::OAuth2MintTokenFlow(URLRequestContextGetter* context,
+ Delegate* delegate,
+ const Parameters& parameters)
+ : OAuth2ApiCallFlow(context,
+ std::string(),
+ parameters.access_token,
+ std::vector<std::string>()),
+ delegate_(delegate),
+ parameters_(parameters),
+ weak_factory_(this) {}
+
+OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
+
+void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token,
+ int time_to_live) {
+ if (delegate_)
+ delegate_->OnMintTokenSuccess(access_token, time_to_live);
+
+ // |this| may already be deleted.
+}
+
+void OAuth2MintTokenFlow::ReportIssueAdviceSuccess(
+ const IssueAdviceInfo& issue_advice) {
+ if (delegate_)
+ delegate_->OnIssueAdviceSuccess(issue_advice);
+
+ // |this| may already be deleted.
+}
+
+void OAuth2MintTokenFlow::ReportFailure(
+ const GoogleServiceAuthError& error) {
+ if (delegate_)
+ delegate_->OnMintTokenFailure(error);
+
+ // |this| may already be deleted.
+}
+
+GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
+ return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url());
+}
+
+std::string OAuth2MintTokenFlow::CreateApiCallBody() {
+ const char* force_value =
+ (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
+ parameters_.mode == MODE_RECORD_GRANT)
+ ? kForceValueTrue : kForceValueFalse;
+ const char* response_type_value =
+ (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
+ parameters_.mode == MODE_MINT_TOKEN_FORCE)
+ ? kResponseTypeValueToken : kResponseTypeValueNone;
+ return base::StringPrintf(
+ kOAuth2IssueTokenBodyFormat,
+ net::EscapeUrlEncodedData(force_value, true).c_str(),
+ net::EscapeUrlEncodedData(response_type_value, true).c_str(),
+ net::EscapeUrlEncodedData(
+ JoinString(parameters_.scopes, ' '), true).c_str(),
+ net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
+ net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str());
+}
+
+void OAuth2MintTokenFlow::ProcessApiCallSuccess(
+ const net::URLFetcher* source) {
+ std::string response_body;
+ source->GetResponseAsString(&response_body);
+ scoped_ptr<base::Value> value(base::JSONReader::Read(response_body));
+ base::DictionaryValue* dict = NULL;
+ if (!value.get() || !value->GetAsDictionary(&dict)) {
+ ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to parse a JSON object from a service response."));
+ return;
+ }
+
+ std::string issue_advice_value;
+ if (!dict->GetString(kIssueAdviceKey, &issue_advice_value)) {
+ ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to find an issueAdvice in a service response."));
+ return;
+ }
+ if (issue_advice_value == kIssueAdviceValueConsent) {
+ IssueAdviceInfo issue_advice;
+ if (ParseIssueAdviceResponse(dict, &issue_advice))
+ ReportIssueAdviceSuccess(issue_advice);
+ else
+ ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to parse the contents of consent "
+ "from a service response."));
+ } else {
+ std::string access_token;
+ int time_to_live;
+ if (ParseMintTokenResponse(dict, &access_token, &time_to_live))
+ ReportSuccess(access_token, time_to_live);
+ else
+ ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
+ "Not able to parse the contents of access token "
+ "from a service response."));
+ }
+
+ // |this| may be deleted!
+}
+
+void OAuth2MintTokenFlow::ProcessApiCallFailure(
+ const net::URLFetcher* source) {
+ ReportFailure(CreateAuthError(source));
+}
+void OAuth2MintTokenFlow::ProcessNewAccessToken(
+ const std::string& access_token) {
+ // We don't currently store new access tokens. We generate one every time.
+ // So we have nothing to do here.
+ return;
+}
+void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure(
+ const GoogleServiceAuthError& error) {
+ ReportFailure(error);
+}
+
+// static
+bool OAuth2MintTokenFlow::ParseMintTokenResponse(
+ const base::DictionaryValue* dict, std::string* access_token,
+ int* time_to_live) {
+ CHECK(dict);
+ CHECK(access_token);
+ CHECK(time_to_live);
+ std::string ttl_string;
+ return dict->GetString(kExpiresInKey, &ttl_string) &&
+ base::StringToInt(ttl_string, time_to_live) &&
+ dict->GetString(kAccessTokenKey, access_token);
+}
+
+// static
+bool OAuth2MintTokenFlow::ParseIssueAdviceResponse(
+ const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) {
+ CHECK(dict);
+ CHECK(issue_advice);
+
+ const base::DictionaryValue* consent_dict = NULL;
+ if (!dict->GetDictionary(kConsentKey, &consent_dict))
+ return false;
+
+ const base::ListValue* scopes_list = NULL;
+ if (!consent_dict->GetList(kScopesKey, &scopes_list))
+ return false;
+
+ bool success = true;
+ for (size_t index = 0; index < scopes_list->GetSize(); ++index) {
+ const base::DictionaryValue* scopes_entry = NULL;
+ IssueAdviceInfoEntry entry;
+ string16 detail;
+ if (!scopes_list->GetDictionary(index, &scopes_entry) ||
+ !scopes_entry->GetString(kDescriptionKey, &entry.description) ||
+ !scopes_entry->GetString(kDetailKey, &detail)) {
+ success = false;
+ break;
+ }
+
+ TrimWhitespace(entry.description, TRIM_ALL, &entry.description);
+ static const string16 detail_separators = ASCIIToUTF16(kDetailSeparators);
+ Tokenize(detail, detail_separators, &entry.details);
+ for (size_t i = 0; i < entry.details.size(); i++)
+ TrimWhitespace(entry.details[i], TRIM_ALL, &entry.details[i]);
+ issue_advice->push_back(entry);
+ }
+
+ if (!success)
+ issue_advice->clear();
+
+ return success;
+}
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow.h b/chromium/google_apis/gaia/oauth2_mint_token_flow.h
new file mode 100644
index 00000000000..5bb751d6c71
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH2_MINT_TOKEN_FLOW_H_
+#define GOOGLE_APIS_GAIA_OAUTH2_MINT_TOKEN_FLOW_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "google_apis/gaia/oauth2_api_call_flow.h"
+
+class GoogleServiceAuthError;
+class OAuth2MintTokenFlowTest;
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class URLFetcher;
+}
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// IssueAdvice: messages to show to the user to get a user's approval.
+// The structure is as follows:
+// * Description 1
+// - Detail 1.1
+// - Details 1.2
+// * Description 2
+// - Detail 2.1
+// - Detail 2.2
+// - Detail 2.3
+// * Description 3
+// - Detail 3.1
+struct IssueAdviceInfoEntry {
+ public:
+ IssueAdviceInfoEntry();
+ ~IssueAdviceInfoEntry();
+
+ string16 description;
+ std::vector<string16> details;
+
+ bool operator==(const IssueAdviceInfoEntry& rhs) const;
+};
+
+typedef std::vector<IssueAdviceInfoEntry> IssueAdviceInfo;
+
+// This class implements the OAuth2 flow to Google to mint an OAuth2
+// token for the given client and the given set of scopes from the
+// OAuthLogin scoped "master" OAuth2 token for the user logged in to
+// Chrome.
+class OAuth2MintTokenFlow : public OAuth2ApiCallFlow {
+ public:
+ // There are four differnt modes when minting a token to grant
+ // access to third-party app for a user.
+ enum Mode {
+ // Get the messages to display to the user without minting a token.
+ MODE_ISSUE_ADVICE,
+ // Record a grant but do not get a token back.
+ MODE_RECORD_GRANT,
+ // Mint a token for an existing grant.
+ MODE_MINT_TOKEN_NO_FORCE,
+ // Mint a token forcefully even if there is no existing grant.
+ MODE_MINT_TOKEN_FORCE,
+ };
+
+ // Parameters needed to mint a token.
+ struct Parameters {
+ public:
+ Parameters();
+ Parameters(const std::string& at,
+ const std::string& eid,
+ const std::string& cid,
+ const std::vector<std::string>& scopes_arg,
+ Mode mode_arg);
+ ~Parameters();
+
+ std::string access_token;
+ std::string extension_id;
+ std::string client_id;
+ std::vector<std::string> scopes;
+ Mode mode;
+ };
+
+ class Delegate {
+ public:
+ virtual void OnMintTokenSuccess(const std::string& access_token,
+ int time_to_live) {}
+ virtual void OnIssueAdviceSuccess(const IssueAdviceInfo& issue_advice) {}
+ virtual void OnMintTokenFailure(const GoogleServiceAuthError& error) {}
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ OAuth2MintTokenFlow(net::URLRequestContextGetter* context,
+ Delegate* delegate,
+ const Parameters& parameters);
+ virtual ~OAuth2MintTokenFlow();
+
+ protected:
+ // Implementation of template methods in OAuth2ApiCallFlow.
+ virtual GURL CreateApiCallUrl() OVERRIDE;
+ virtual std::string CreateApiCallBody() OVERRIDE;
+
+ virtual void ProcessApiCallSuccess(
+ const net::URLFetcher* source) OVERRIDE;
+ virtual void ProcessApiCallFailure(
+ const net::URLFetcher* source) OVERRIDE;
+ virtual void ProcessNewAccessToken(const std::string& access_token) OVERRIDE;
+ virtual void ProcessMintAccessTokenFailure(
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ private:
+ friend class OAuth2MintTokenFlowTest;
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest, CreateApiCallBody);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest, ParseIssueAdviceResponse);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest, ParseMintTokenResponse);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest, ProcessApiCallSuccess);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest, ProcessApiCallFailure);
+ FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFlowTest,
+ ProcessMintAccessTokenFailure);
+
+ void ReportSuccess(const std::string& access_token, int time_to_live);
+ void ReportIssueAdviceSuccess(const IssueAdviceInfo& issue_advice);
+ void ReportFailure(const GoogleServiceAuthError& error);
+
+ static bool ParseIssueAdviceResponse(
+ const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice);
+ static bool ParseMintTokenResponse(
+ const base::DictionaryValue* dict, std::string* access_token,
+ int* time_to_live);
+
+ Delegate* delegate_;
+ Parameters parameters_;
+ base::WeakPtrFactory<OAuth2MintTokenFlow> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2MintTokenFlow);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_MINT_TOKEN_FLOW_H_
diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc b/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
new file mode 100644
index 00000000000..7cd3d672c62
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth2_mint_token_flow_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright (c) 2012 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.
+//
+// A complete set of unit tests for OAuth2MintTokenFlow.
+
+#include <string>
+#include <vector>
+
+#include "base/json/json_reader.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/oauth2_mint_token_flow.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using net::TestURLFetcher;
+using net::URLFetcher;
+using net::URLRequestStatus;
+using testing::_;
+using testing::StrictMock;
+
+namespace {
+
+static const char kValidTokenResponse[] =
+ "{"
+ " \"token\": \"at1\","
+ " \"issueAdvice\": \"Auto\","
+ " \"expiresIn\": \"3600\""
+ "}";
+static const char kTokenResponseNoAccessToken[] =
+ "{"
+ " \"issueAdvice\": \"Auto\""
+ "}";
+
+static const char kValidIssueAdviceResponse[] =
+ "{"
+ " \"issueAdvice\": \"consent\","
+ " \"consent\": {"
+ " \"oauthClient\": {"
+ " \"name\": \"Test app\","
+ " \"iconUri\": \"\","
+ " \"developerEmail\": \"munjal@chromium.org\""
+ " },"
+ " \"scopes\": ["
+ " {"
+ " \"description\": \"Manage your calendars\","
+ " \"detail\": \"\nView and manage your calendars\n\""
+ " },"
+ " {"
+ " \"description\": \"Manage your documents\","
+ " \"detail\": \"\nView your documents\nUpload new documents\n\""
+ " }"
+ " ]"
+ " }"
+ "}";
+
+static const char kIssueAdviceResponseNoDescription[] =
+ "{"
+ " \"issueAdvice\": \"consent\","
+ " \"consent\": {"
+ " \"oauthClient\": {"
+ " \"name\": \"Test app\","
+ " \"iconUri\": \"\","
+ " \"developerEmail\": \"munjal@chromium.org\""
+ " },"
+ " \"scopes\": ["
+ " {"
+ " \"description\": \"Manage your calendars\","
+ " \"detail\": \"\nView and manage your calendars\n\""
+ " },"
+ " {"
+ " \"detail\": \"\nView your documents\nUpload new documents\n\""
+ " }"
+ " ]"
+ " }"
+ "}";
+
+static const char kIssueAdviceResponseNoDetail[] =
+ "{"
+ " \"issueAdvice\": \"consent\","
+ " \"consent\": {"
+ " \"oauthClient\": {"
+ " \"name\": \"Test app\","
+ " \"iconUri\": \"\","
+ " \"developerEmail\": \"munjal@chromium.org\""
+ " },"
+ " \"scopes\": ["
+ " {"
+ " \"description\": \"Manage your calendars\","
+ " \"detail\": \"\nView and manage your calendars\n\""
+ " },"
+ " {"
+ " \"description\": \"Manage your documents\""
+ " }"
+ " ]"
+ " }"
+ "}";
+
+std::vector<std::string> CreateTestScopes() {
+ std::vector<std::string> scopes;
+ scopes.push_back("http://scope1");
+ scopes.push_back("http://scope2");
+ return scopes;
+}
+
+static IssueAdviceInfo CreateIssueAdvice() {
+ IssueAdviceInfo ia;
+ IssueAdviceInfoEntry e1;
+ e1.description = ASCIIToUTF16("Manage your calendars");
+ e1.details.push_back(ASCIIToUTF16("View and manage your calendars"));
+ ia.push_back(e1);
+ IssueAdviceInfoEntry e2;
+ e2.description = ASCIIToUTF16("Manage your documents");
+ e2.details.push_back(ASCIIToUTF16("View your documents"));
+ e2.details.push_back(ASCIIToUTF16("Upload new documents"));
+ ia.push_back(e2);
+ return ia;
+}
+
+class MockDelegate : public OAuth2MintTokenFlow::Delegate {
+ public:
+ MockDelegate() {}
+ ~MockDelegate() {}
+
+ MOCK_METHOD2(OnMintTokenSuccess, void(const std::string& access_token,
+ int time_to_live));
+ MOCK_METHOD1(OnIssueAdviceSuccess,
+ void (const IssueAdviceInfo& issue_advice));
+ MOCK_METHOD1(OnMintTokenFailure,
+ void(const GoogleServiceAuthError& error));
+};
+
+class MockMintTokenFlow : public OAuth2MintTokenFlow {
+ public:
+ explicit MockMintTokenFlow(MockDelegate* delegate,
+ const OAuth2MintTokenFlow::Parameters& parameters )
+ : OAuth2MintTokenFlow(NULL, delegate, parameters) {}
+ ~MockMintTokenFlow() {}
+
+ MOCK_METHOD0(CreateAccessTokenFetcher, OAuth2AccessTokenFetcher*());
+};
+
+} // namespace
+
+class OAuth2MintTokenFlowTest : public testing::Test {
+ public:
+ OAuth2MintTokenFlowTest() {}
+ virtual ~OAuth2MintTokenFlowTest() { }
+
+ protected:
+ void CreateFlow(OAuth2MintTokenFlow::Mode mode) {
+ return CreateFlow(&delegate_, mode);
+ }
+
+ void CreateFlow(MockDelegate* delegate,
+ OAuth2MintTokenFlow::Mode mode) {
+ std::string rt = "refresh_token";
+ std::string ext_id = "ext1";
+ std::string client_id = "client1";
+ std::vector<std::string> scopes(CreateTestScopes());
+ flow_.reset(new MockMintTokenFlow(
+ delegate,
+ OAuth2MintTokenFlow::Parameters(rt, ext_id, client_id, scopes, mode)));
+ }
+
+ // Helper to parse the given string to DictionaryValue.
+ static base::DictionaryValue* ParseJson(const std::string& str) {
+ scoped_ptr<Value> value(base::JSONReader::Read(str));
+ EXPECT_TRUE(value.get());
+ EXPECT_EQ(Value::TYPE_DICTIONARY, value->GetType());
+ return static_cast<base::DictionaryValue*>(value.release());
+ }
+
+ scoped_ptr<MockMintTokenFlow> flow_;
+ StrictMock<MockDelegate> delegate_;
+};
+
+TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
+ { // Issue advice mode.
+ CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE);
+ std::string body = flow_->CreateApiCallBody();
+ std::string expected_body(
+ "force=false"
+ "&response_type=none"
+ "&scope=http://scope1+http://scope2"
+ "&client_id=client1"
+ "&origin=ext1");
+ EXPECT_EQ(expected_body, body);
+ }
+ { // Record grant mode.
+ CreateFlow(OAuth2MintTokenFlow::MODE_RECORD_GRANT);
+ std::string body = flow_->CreateApiCallBody();
+ std::string expected_body(
+ "force=true"
+ "&response_type=none"
+ "&scope=http://scope1+http://scope2"
+ "&client_id=client1"
+ "&origin=ext1");
+ EXPECT_EQ(expected_body, body);
+ }
+ { // Mint token no force mode.
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ std::string body = flow_->CreateApiCallBody();
+ std::string expected_body(
+ "force=false"
+ "&response_type=token"
+ "&scope=http://scope1+http://scope2"
+ "&client_id=client1"
+ "&origin=ext1");
+ EXPECT_EQ(expected_body, body);
+ }
+ { // Mint token force mode.
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE);
+ std::string body = flow_->CreateApiCallBody();
+ std::string expected_body(
+ "force=true"
+ "&response_type=token"
+ "&scope=http://scope1+http://scope2"
+ "&client_id=client1"
+ "&origin=ext1");
+ EXPECT_EQ(expected_body, body);
+ }
+}
+
+TEST_F(OAuth2MintTokenFlowTest, ParseMintTokenResponse) {
+ { // Access token missing.
+ scoped_ptr<base::DictionaryValue> json(
+ ParseJson(kTokenResponseNoAccessToken));
+ std::string at;
+ int ttl;
+ EXPECT_FALSE(OAuth2MintTokenFlow::ParseMintTokenResponse(json.get(), &at,
+ &ttl));
+ EXPECT_TRUE(at.empty());
+ }
+ { // All good.
+ scoped_ptr<base::DictionaryValue> json(ParseJson(kValidTokenResponse));
+ std::string at;
+ int ttl;
+ EXPECT_TRUE(OAuth2MintTokenFlow::ParseMintTokenResponse(json.get(), &at,
+ &ttl));
+ EXPECT_EQ("at1", at);
+ EXPECT_EQ(3600, ttl);
+ }
+}
+
+TEST_F(OAuth2MintTokenFlowTest, ParseIssueAdviceResponse) {
+ { // Description missing.
+ scoped_ptr<base::DictionaryValue> json(
+ ParseJson(kIssueAdviceResponseNoDescription));
+ IssueAdviceInfo ia;
+ EXPECT_FALSE(OAuth2MintTokenFlow::ParseIssueAdviceResponse(
+ json.get(), &ia));
+ EXPECT_TRUE(ia.empty());
+ }
+ { // Detail missing.
+ scoped_ptr<base::DictionaryValue> json(
+ ParseJson(kIssueAdviceResponseNoDetail));
+ IssueAdviceInfo ia;
+ EXPECT_FALSE(OAuth2MintTokenFlow::ParseIssueAdviceResponse(
+ json.get(), &ia));
+ EXPECT_TRUE(ia.empty());
+ }
+ { // All good.
+ scoped_ptr<base::DictionaryValue> json(
+ ParseJson(kValidIssueAdviceResponse));
+ IssueAdviceInfo ia;
+ EXPECT_TRUE(OAuth2MintTokenFlow::ParseIssueAdviceResponse(
+ json.get(), &ia));
+ IssueAdviceInfo ia_expected(CreateIssueAdvice());
+ EXPECT_EQ(ia_expected, ia);
+ }
+}
+
+TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallSuccess) {
+ { // No body.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(std::string());
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Bad json.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString("foo");
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Valid json: no access token.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Valid json: good token response.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(kValidTokenResponse);
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenSuccess("at1", 3600));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Valid json: no description.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(kIssueAdviceResponseNoDescription);
+ CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Valid json: no detail.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(kIssueAdviceResponseNoDetail);
+ CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+ { // Valid json: good issue advice response.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.SetResponseString(kValidIssueAdviceResponse);
+ CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE);
+ IssueAdviceInfo ia(CreateIssueAdvice());
+ EXPECT_CALL(delegate_, OnIssueAdviceSuccess(ia));
+ flow_->ProcessApiCallSuccess(&url_fetcher);
+ }
+}
+
+TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallFailure) {
+ { // Null delegate should work fine.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.set_status(URLRequestStatus(URLRequestStatus::FAILED, 101));
+ CreateFlow(NULL, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ flow_->ProcessApiCallFailure(&url_fetcher);
+ }
+
+ { // Non-null delegate.
+ TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL);
+ url_fetcher.set_status(URLRequestStatus(URLRequestStatus::FAILED, 101));
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(_));
+ flow_->ProcessApiCallFailure(&url_fetcher);
+ }
+}
+
+TEST_F(OAuth2MintTokenFlowTest, ProcessMintAccessTokenFailure) {
+ { // Null delegate should work fine.
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::FromConnectionError(101));
+ CreateFlow(NULL, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ flow_->ProcessMintAccessTokenFailure(error);
+ }
+
+ { // Non-null delegate.
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::FromConnectionError(101));
+ CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE);
+ EXPECT_CALL(delegate_, OnMintTokenFailure(error));
+ flow_->ProcessMintAccessTokenFailure(error);
+ }
+}
diff --git a/chromium/google_apis/gaia/oauth_request_signer.cc b/chromium/google_apis/gaia/oauth_request_signer.cc
new file mode 100644
index 00000000000..b16744cbccd
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth_request_signer.cc
@@ -0,0 +1,459 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/gaia/oauth_request_signer.h"
+
+#include <cctype>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <map>
+#include <string>
+
+#include "base/base64.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+#include "crypto/hmac.h"
+#include "url/gurl.h"
+
+namespace {
+
+static const int kHexBase = 16;
+static char kHexDigits[] = "0123456789ABCDEF";
+static const size_t kHmacDigestLength = 20;
+static const int kMaxNonceLength = 30;
+static const int kMinNonceLength = 15;
+
+static const char kOAuthConsumerKeyLabel[] = "oauth_consumer_key";
+static const char kOAuthConsumerSecretLabel[] = "oauth_consumer_secret";
+static const char kOAuthNonceCharacters[] =
+ "abcdefghijklmnopqrstuvwyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWYZ"
+ "0123456789_";
+static const char kOAuthNonceLabel[] = "oauth_nonce";
+static const char kOAuthSignatureLabel[] = "oauth_signature";
+static const char kOAuthSignatureMethodLabel[] = "oauth_signature_method";
+static const char kOAuthTimestampLabel[] = "oauth_timestamp";
+static const char kOAuthTokenLabel[] = "oauth_token";
+static const char kOAuthTokenSecretLabel[] = "oauth_token_secret";
+static const char kOAuthVersion[] = "1.0";
+static const char kOAuthVersionLabel[] = "oauth_version";
+
+enum ParseQueryState {
+ START_STATE,
+ KEYWORD_STATE,
+ VALUE_STATE,
+};
+
+const std::string HttpMethodName(OAuthRequestSigner::HttpMethod method) {
+ switch (method) {
+ case OAuthRequestSigner::GET_METHOD:
+ return "GET";
+ case OAuthRequestSigner::POST_METHOD:
+ return "POST";
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+const std::string SignatureMethodName(
+ OAuthRequestSigner::SignatureMethod method) {
+ switch (method) {
+ case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
+ return "HMAC-SHA1";
+ case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
+ return "RSA-SHA1";
+ case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
+ return "PLAINTEXT";
+ }
+ NOTREACHED();
+ return std::string();
+}
+
+std::string BuildBaseString(const GURL& request_base_url,
+ OAuthRequestSigner::HttpMethod http_method,
+ const std::string& base_parameters) {
+ return base::StringPrintf("%s&%s&%s",
+ HttpMethodName(http_method).c_str(),
+ OAuthRequestSigner::Encode(
+ request_base_url.spec()).c_str(),
+ OAuthRequestSigner::Encode(
+ base_parameters).c_str());
+}
+
+std::string BuildBaseStringParameters(
+ const OAuthRequestSigner::Parameters& parameters) {
+ std::string result;
+ OAuthRequestSigner::Parameters::const_iterator cursor;
+ OAuthRequestSigner::Parameters::const_iterator limit;
+ bool first = true;
+ for (cursor = parameters.begin(), limit = parameters.end();
+ cursor != limit;
+ ++cursor) {
+ if (first)
+ first = false;
+ else
+ result += '&';
+ result += OAuthRequestSigner::Encode(cursor->first);
+ result += '=';
+ result += OAuthRequestSigner::Encode(cursor->second);
+ }
+ return result;
+}
+
+std::string GenerateNonce() {
+ char result[kMaxNonceLength + 1];
+ int length = base::RandUint64() % (kMaxNonceLength - kMinNonceLength + 1) +
+ kMinNonceLength;
+ result[length] = '\0';
+ for (int index = 0; index < length; ++index)
+ result[index] = kOAuthNonceCharacters[
+ base::RandUint64() % (sizeof(kOAuthNonceCharacters) - 1)];
+ return result;
+}
+
+std::string GenerateTimestamp() {
+ return base::StringPrintf(
+ "%" PRId64,
+ (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()).InSeconds());
+}
+
+// Creates a string-to-string, keyword-value map from a parameter/query string
+// that uses ampersand (&) to seperate paris and equals (=) to seperate
+// keyword from value.
+bool ParseQuery(const std::string& query,
+ OAuthRequestSigner::Parameters* parameters_result) {
+ std::string::const_iterator cursor;
+ std::string keyword;
+ std::string::const_iterator limit;
+ OAuthRequestSigner::Parameters parameters;
+ ParseQueryState state;
+ std::string value;
+
+ state = START_STATE;
+ for (cursor = query.begin(), limit = query.end();
+ cursor != limit;
+ ++cursor) {
+ char character = *cursor;
+ switch (state) {
+ case KEYWORD_STATE:
+ switch (character) {
+ case '&':
+ parameters[keyword] = value;
+ keyword = "";
+ value = "";
+ state = START_STATE;
+ break;
+ case '=':
+ state = VALUE_STATE;
+ break;
+ default:
+ keyword += character;
+ }
+ break;
+ case START_STATE:
+ switch (character) {
+ case '&': // Intentionally falling through
+ case '=':
+ return false;
+ default:
+ keyword += character;
+ state = KEYWORD_STATE;
+ }
+ break;
+ case VALUE_STATE:
+ switch (character) {
+ case '=':
+ return false;
+ case '&':
+ parameters[keyword] = value;
+ keyword = "";
+ value = "";
+ state = START_STATE;
+ break;
+ default:
+ value += character;
+ }
+ break;
+ }
+ }
+ switch (state) {
+ case START_STATE:
+ break;
+ case KEYWORD_STATE: // Intentionally falling through
+ case VALUE_STATE:
+ parameters[keyword] = value;
+ break;
+ default:
+ NOTREACHED();
+ }
+ *parameters_result = parameters;
+ return true;
+}
+
+// Creates the value for the oauth_signature parameter when the
+// oauth_signature_method is HMAC-SHA1.
+bool SignHmacSha1(const std::string& text,
+ const std::string& key,
+ std::string* signature_return) {
+ crypto::HMAC hmac(crypto::HMAC::SHA1);
+ DCHECK(hmac.DigestLength() == kHmacDigestLength);
+ unsigned char digest[kHmacDigestLength];
+ bool result = hmac.Init(key) &&
+ hmac.Sign(text, digest, kHmacDigestLength) &&
+ base::Base64Encode(std::string(reinterpret_cast<const char*>(digest),
+ kHmacDigestLength),
+ signature_return);
+ return result;
+}
+
+// Creates the value for the oauth_signature parameter when the
+// oauth_signature_method is PLAINTEXT.
+//
+// Not yet implemented, and might never be.
+bool SignPlaintext(const std::string& text,
+ const std::string& key,
+ std::string* result) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+// Creates the value for the oauth_signature parameter when the
+// oauth_signature_method is RSA-SHA1.
+//
+// Not yet implemented, and might never be.
+bool SignRsaSha1(const std::string& text,
+ const std::string& key,
+ std::string* result) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+// Adds parameters that are required by OAuth added as needed to |parameters|.
+void PrepareParameters(OAuthRequestSigner::Parameters* parameters,
+ OAuthRequestSigner::SignatureMethod signature_method,
+ OAuthRequestSigner::HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& token_key) {
+ if (parameters->find(kOAuthNonceLabel) == parameters->end())
+ (*parameters)[kOAuthNonceLabel] = GenerateNonce();
+
+ if (parameters->find(kOAuthTimestampLabel) == parameters->end())
+ (*parameters)[kOAuthTimestampLabel] = GenerateTimestamp();
+
+ (*parameters)[kOAuthConsumerKeyLabel] = consumer_key;
+ (*parameters)[kOAuthSignatureMethodLabel] =
+ SignatureMethodName(signature_method);
+ (*parameters)[kOAuthTokenLabel] = token_key;
+ (*parameters)[kOAuthVersionLabel] = kOAuthVersion;
+}
+
+// Implements shared signing logic, generating the signature and storing it in
+// |parameters|. Returns true if the signature has been generated succesfully.
+bool SignParameters(const GURL& request_base_url,
+ OAuthRequestSigner::SignatureMethod signature_method,
+ OAuthRequestSigner::HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ OAuthRequestSigner::Parameters* parameters) {
+ DCHECK(request_base_url.is_valid());
+ PrepareParameters(parameters, signature_method, http_method,
+ consumer_key, token_key);
+ std::string base_parameters = BuildBaseStringParameters(*parameters);
+ std::string base = BuildBaseString(request_base_url, http_method,
+ base_parameters);
+ std::string key = consumer_secret + '&' + token_secret;
+ bool is_signed = false;
+ std::string signature;
+ switch (signature_method) {
+ case OAuthRequestSigner::HMAC_SHA1_SIGNATURE:
+ is_signed = SignHmacSha1(base, key, &signature);
+ break;
+ case OAuthRequestSigner::RSA_SHA1_SIGNATURE:
+ is_signed = SignRsaSha1(base, key, &signature);
+ break;
+ case OAuthRequestSigner::PLAINTEXT_SIGNATURE:
+ is_signed = SignPlaintext(base, key, &signature);
+ break;
+ default:
+ NOTREACHED();
+ }
+ if (is_signed)
+ (*parameters)[kOAuthSignatureLabel] = signature;
+ return is_signed;
+}
+
+
+} // namespace
+
+// static
+bool OAuthRequestSigner::Decode(const std::string& text,
+ std::string* decoded_text) {
+ std::string accumulator;
+ std::string::const_iterator cursor;
+ std::string::const_iterator limit;
+ for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
+ char character = *cursor;
+ if (character == '%') {
+ ++cursor;
+ if (cursor == limit)
+ return false;
+ char* first = strchr(kHexDigits, *cursor);
+ if (!first)
+ return false;
+ int high = first - kHexDigits;
+ DCHECK(high >= 0 && high < kHexBase);
+
+ ++cursor;
+ if (cursor == limit)
+ return false;
+ char* second = strchr(kHexDigits, *cursor);
+ if (!second)
+ return false;
+ int low = second - kHexDigits;
+ DCHECK(low >= 0 || low < kHexBase);
+
+ char decoded = static_cast<char>(high * kHexBase + low);
+ DCHECK(!(IsAsciiAlpha(decoded) || IsAsciiDigit(decoded)));
+ DCHECK(!(decoded && strchr("-._~", decoded)));
+ accumulator += decoded;
+ } else {
+ accumulator += character;
+ }
+ }
+ *decoded_text = accumulator;
+ return true;
+}
+
+// static
+std::string OAuthRequestSigner::Encode(const std::string& text) {
+ std::string result;
+ std::string::const_iterator cursor;
+ std::string::const_iterator limit;
+ for (limit = text.end(), cursor = text.begin(); cursor != limit; ++cursor) {
+ char character = *cursor;
+ if (IsAsciiAlpha(character) || IsAsciiDigit(character)) {
+ result += character;
+ } else {
+ switch (character) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ result += character;
+ break;
+ default:
+ unsigned char byte = static_cast<unsigned char>(character);
+ result = result + '%' + kHexDigits[byte / kHexBase] +
+ kHexDigits[byte % kHexBase];
+ }
+ }
+ }
+ return result;
+}
+
+// static
+bool OAuthRequestSigner::ParseAndSign(const GURL& request_url_with_parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* result) {
+ DCHECK(request_url_with_parameters.is_valid());
+ Parameters parameters;
+ if (request_url_with_parameters.has_query()) {
+ const std::string& query = request_url_with_parameters.query();
+ if (!query.empty()) {
+ if (!ParseQuery(query, &parameters))
+ return false;
+ }
+ }
+ std::string spec = request_url_with_parameters.spec();
+ std::string url_without_parameters = spec;
+ std::string::size_type question = spec.find("?");
+ if (question != std::string::npos)
+ url_without_parameters = spec.substr(0,question);
+ return SignURL(GURL(url_without_parameters), parameters, signature_method,
+ http_method, consumer_key, consumer_secret, token_key,
+ token_secret, result);
+}
+
+// static
+bool OAuthRequestSigner::SignURL(
+ const GURL& request_base_url,
+ const Parameters& request_parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* signed_text_return) {
+ DCHECK(request_base_url.is_valid());
+ Parameters parameters(request_parameters);
+ bool is_signed = SignParameters(request_base_url, signature_method,
+ http_method, consumer_key, consumer_secret,
+ token_key, token_secret, &parameters);
+ if (is_signed) {
+ std::string signed_text;
+ switch (http_method) {
+ case GET_METHOD:
+ signed_text = request_base_url.spec() + '?';
+ // Intentionally falling through
+ case POST_METHOD:
+ signed_text += BuildBaseStringParameters(parameters);
+ break;
+ default:
+ NOTREACHED();
+ }
+ *signed_text_return = signed_text;
+ }
+ return is_signed;
+}
+
+// static
+bool OAuthRequestSigner::SignAuthHeader(
+ const GURL& request_base_url,
+ const Parameters& request_parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* signed_text_return) {
+ DCHECK(request_base_url.is_valid());
+ Parameters parameters(request_parameters);
+ bool is_signed = SignParameters(request_base_url, signature_method,
+ http_method, consumer_key, consumer_secret,
+ token_key, token_secret, &parameters);
+ if (is_signed) {
+ std::string signed_text = "OAuth ";
+ bool first = true;
+ for (Parameters::const_iterator param = parameters.begin();
+ param != parameters.end();
+ ++param) {
+ if (first)
+ first = false;
+ else
+ signed_text += ", ";
+ signed_text +=
+ base::StringPrintf(
+ "%s=\"%s\"",
+ OAuthRequestSigner::Encode(param->first).c_str(),
+ OAuthRequestSigner::Encode(param->second).c_str());
+ }
+ *signed_text_return = signed_text;
+ }
+ return is_signed;
+}
diff --git a/chromium/google_apis/gaia/oauth_request_signer.h b/chromium/google_apis/gaia/oauth_request_signer.h
new file mode 100644
index 00000000000..3b91d4dbace
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth_request_signer.h
@@ -0,0 +1,100 @@
+// Copyright (c) 2011 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.
+
+#ifndef GOOGLE_APIS_GAIA_OAUTH_REQUEST_SIGNER_H_
+#define GOOGLE_APIS_GAIA_OAUTH_REQUEST_SIGNER_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+
+class GURL;
+
+// Implements the OAuth request signing process as described here:
+// http://oauth.net/core/1.0/#signing_process
+//
+// NOTE: Currently the only supported SignatureMethod is HMAC_SHA1_SIGNATURE
+class OAuthRequestSigner {
+ public:
+ enum SignatureMethod {
+ HMAC_SHA1_SIGNATURE,
+ RSA_SHA1_SIGNATURE,
+ PLAINTEXT_SIGNATURE
+ };
+
+ enum HttpMethod {
+ GET_METHOD,
+ POST_METHOD
+ };
+
+ typedef std::map<std::string,std::string> Parameters;
+
+ // Percent encoding and decoding for OAuth.
+ //
+ // The form of percent encoding used for OAuth request signing is very
+ // specific and strict. See http://oauth.net/core/1.0/#encoding_parameters.
+ // This definition is considered the current standard as of January 2005.
+ // While as of July 2011 many systems to do not comply, any valid OAuth
+ // implementation must comply.
+ //
+ // Any character which is in the "unreserved set" MUST NOT be encoded.
+ // All other characters MUST be encoded.
+ //
+ // The unreserved set is comprised of the alphanumeric characters and these
+ // others:
+ // - minus (-)
+ // - period (.)
+ // - underscore (_)
+ // - tilde (~)
+ static bool Decode(const std::string& text, std::string* decoded_text);
+ static std::string Encode(const std::string& text);
+
+ // Signs a request specified as URL string, complete with parameters.
+ //
+ // If HttpMethod is GET_METHOD, the signed result is the full URL, otherwise
+ // it is the request parameters, including the oauth_signature field.
+ static bool ParseAndSign(const GURL& request_url_with_parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* signed_result);
+
+ // Signs a request specified as the combination of a base URL string, with
+ // parameters included in a separate map data structure. NOTE: The base URL
+ // string must not contain a question mark (?) character. If it does,
+ // you can use ParseAndSign() instead.
+ //
+ // If HttpMethod is GET_METHOD, the signed result is the full URL, otherwise
+ // it is the request parameters, including the oauth_signature field.
+ static bool SignURL(const GURL& request_base_url,
+ const Parameters& parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* signed_result);
+
+ // Similar to SignURL(), but the returned string is not a URL, but the payload
+ // to for an HTTP Authorization header.
+ static bool SignAuthHeader(const GURL& request_base_url,
+ const Parameters& parameters,
+ SignatureMethod signature_method,
+ HttpMethod http_method,
+ const std::string& consumer_key,
+ const std::string& consumer_secret,
+ const std::string& token_key,
+ const std::string& token_secret,
+ std::string* signed_result);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(OAuthRequestSigner);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH_REQUEST_SIGNER_H_
diff --git a/chromium/google_apis/gaia/oauth_request_signer_unittest.cc b/chromium/google_apis/gaia/oauth_request_signer_unittest.cc
new file mode 100644
index 00000000000..30358e0aa54
--- /dev/null
+++ b/chromium/google_apis/gaia/oauth_request_signer_unittest.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2011 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.
+
+#include "google_apis/gaia/oauth_request_signer.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// This value is used to seed the PRNG at the beginning of a sequence of
+// operations to produce a repeatable sequence.
+#define RANDOM_SEED (0x69E3C47D)
+
+TEST(OAuthRequestSignerTest, Encode) {
+ ASSERT_EQ(OAuthRequestSigner::Encode("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~"),
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~");
+ ASSERT_EQ(OAuthRequestSigner::Encode(
+ "https://accounts.google.com/OAuthLogin"),
+ "https%3A%2F%2Faccounts.google.com%2FOAuthLogin");
+ ASSERT_EQ(OAuthRequestSigner::Encode("%"), "%25");
+ ASSERT_EQ(OAuthRequestSigner::Encode("%25"), "%2525");
+ ASSERT_EQ(OAuthRequestSigner::Encode(
+ "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed "
+ "do eiusmod tempor incididunt ut labore et dolore magna "
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
+ "ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
+ "aute irure dolor in reprehenderit in voluptate velit esse "
+ "cillum dolore eu fugiat nulla pariatur. Excepteur sint "
+ "occaecat cupidatat non proident, sunt in culpa qui officia "
+ "deserunt mollit anim id est laborum."),
+ "Lorem%20ipsum%20dolor%20sit%20amet%2C%20consectetur%20"
+ "adipisicing%20elit%2C%20sed%20do%20eiusmod%20tempor%20"
+ "incididunt%20ut%20labore%20et%20dolore%20magna%20aliqua.%20Ut%20"
+ "enim%20ad%20minim%20veniam%2C%20quis%20nostrud%20exercitation%20"
+ "ullamco%20laboris%20nisi%20ut%20aliquip%20ex%20ea%20commodo%20"
+ "consequat.%20Duis%20aute%20irure%20dolor%20in%20reprehenderit%20"
+ "in%20voluptate%20velit%20esse%20cillum%20dolore%20eu%20fugiat%20"
+ "nulla%20pariatur.%20Excepteur%20sint%20occaecat%20cupidatat%20"
+ "non%20proident%2C%20sunt%20in%20culpa%20qui%20officia%20"
+ "deserunt%20mollit%20anim%20id%20est%20laborum.");
+ ASSERT_EQ(OAuthRequestSigner::Encode("!5}&QF~0R-Ecy[?2Cig>6g=;hH!\\Ju4K%UK;"),
+ "%215%7D%26QF~0R-Ecy%5B%3F2Cig%3E6g%3D%3BhH%21%5CJu4K%25UK%3B");
+ ASSERT_EQ(OAuthRequestSigner::Encode("1UgHf(r)SkMRS`fRZ/8PsTcXT0:\\<9I=6{|:"),
+ "1UgHf%28r%29SkMRS%60fRZ%2F8PsTcXT0%3A%5C%3C9I%3D6%7B%7C%3A");
+ ASSERT_EQ(OAuthRequestSigner::Encode("|<XIy1?o`r\"RuGSX#!:MeP&RLZQM@:\\';2X"),
+ "%7C%3CXIy1%3Fo%60r%22RuGSX%23%21%3AMeP%26RLZQM%40%3A%5C%27%3B2X");
+ ASSERT_EQ(OAuthRequestSigner::Encode("#a@A>ZtcQ/yb.~^Q_]daRT?ffK>@A:afWuZL"),
+ "%23a%40A%3EZtcQ%2Fyb.~%5EQ_%5DdaRT%3FffK%3E%40A%3AafWuZL");
+}
+
+TEST(OAuthRequestSignerTest, DecodeEncoded) {
+ srand(RANDOM_SEED);
+ static const int kIterations = 500;
+ static const int kLengthLimit = 500;
+ for (int iteration = 0; iteration < kIterations; ++iteration) {
+ std::string text;
+ int length = rand() % kLengthLimit;
+ for (int position = 0; position < length; ++position) {
+ text += static_cast<char>(rand() % 256);
+ }
+ std::string encoded = OAuthRequestSigner::Encode(text);
+ std::string decoded;
+ ASSERT_TRUE(OAuthRequestSigner::Decode(encoded, &decoded));
+ ASSERT_EQ(decoded, text);
+ }
+}
+
+TEST(OAuthRequestSignerTest, SignGet1) {
+ GURL request_url("https://www.google.com/accounts/o8/GetOAuthToken");
+ OAuthRequestSigner::Parameters parameters;
+ parameters["scope"] = "https://accounts.google.com/OAuthLogin";
+ parameters["oauth_nonce"] = "2oiE_aHdk5qRTz0L9C8Lq0g";
+ parameters["xaouth_display_name"] = "Chromium";
+ parameters["oauth_timestamp"] = "1308152953";
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::SignURL(
+ request_url,
+ parameters,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::GET_METHOD,
+ "johndoe", // oauth_consumer_key
+ "53cR3t", // consumer secret
+ "4/VGY0MsQadcmO8VnCv9gnhoEooq1v", // oauth_token
+ "c5e0531ff55dfbb4054e", // token secret
+ &signed_text));
+ ASSERT_EQ("https://www.google.com/accounts/o8/GetOAuthToken"
+ "?oauth_consumer_key=johndoe"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&oauth_signature=PFqDTaiyey1UObcvOyI4Ng2HXW0%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308152953"
+ "&oauth_token=4%2FVGY0MsQadcmO8VnCv9gnhoEooq1v"
+ "&oauth_version=1.0"
+ "&scope=https%3A%2F%2Faccounts.google.com%2FOAuthLogin"
+ "&xaouth_display_name=Chromium",
+ signed_text);
+}
+
+TEST(OAuthRequestSignerTest, SignGet2) {
+ GURL request_url("https://accounts.google.com/OAuthGetAccessToken");
+ OAuthRequestSigner::Parameters parameters;
+ parameters["oauth_timestamp"] = "1308147831";
+ parameters["oauth_nonce"] = "4d4hZW9DygWQujP2tz06UN";
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::SignURL(
+ request_url,
+ parameters,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::GET_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/CcC-hgdj1TNnWaX8NTQ76YDXCBEK", // oauth_token
+ std::string(), // token secret
+ &signed_text));
+ ASSERT_EQ(signed_text,
+ "https://accounts.google.com/OAuthGetAccessToken"
+ "?oauth_consumer_key=anonymous"
+ "&oauth_nonce=4d4hZW9DygWQujP2tz06UN"
+ "&oauth_signature=YiJv%2BEOWsvCDCi13%2FhQBFrr0J7c%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308147831"
+ "&oauth_token=4%2FCcC-hgdj1TNnWaX8NTQ76YDXCBEK"
+ "&oauth_version=1.0");
+}
+
+TEST(OAuthRequestSignerTest, ParseAndSignGet1) {
+ GURL request_url("https://www.google.com/accounts/o8/GetOAuthToken"
+ "?scope=https://accounts.google.com/OAuthLogin"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&xaouth_display_name=Chromium"
+ "&oauth_timestamp=1308152953");
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::ParseAndSign(
+ request_url,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::GET_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/CcC-hgdj1TNnWaX8NTQ76YDXCBEK", // oauth_token
+ std::string(), // token secret
+ &signed_text));
+ ASSERT_EQ("https://www.google.com/accounts/o8/GetOAuthToken"
+ "?oauth_consumer_key=anonymous"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&oauth_signature=PH7KP6cP%2BzZ1SJ6WGqBgXwQP9Mc%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308152953"
+ "&oauth_token=4%2FCcC-hgdj1TNnWaX8NTQ76YDXCBEK"
+ "&oauth_version=1.0"
+ "&scope=https%3A%2F%2Faccounts.google.com%2FOAuthLogin"
+ "&xaouth_display_name=Chromium",
+ signed_text);
+}
+
+TEST(OAuthRequestSignerTest, ParseAndSignGet2) {
+ GURL request_url("https://accounts.google.com/OAuthGetAccessToken"
+ "?oauth_timestamp=1308147831"
+ "&oauth_nonce=4d4hZW9DygWQujP2tz06UN");
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::ParseAndSign(
+ request_url,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::GET_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/CcC-hgdj1TNnWaX8NTQ76YDXCBEK", // oauth_token
+ std::string(), // token secret
+ &signed_text));
+ ASSERT_EQ(signed_text,
+ "https://accounts.google.com/OAuthGetAccessToken"
+ "?oauth_consumer_key=anonymous"
+ "&oauth_nonce=4d4hZW9DygWQujP2tz06UN"
+ "&oauth_signature=YiJv%2BEOWsvCDCi13%2FhQBFrr0J7c%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308147831"
+ "&oauth_token=4%2FCcC-hgdj1TNnWaX8NTQ76YDXCBEK"
+ "&oauth_version=1.0");
+}
+
+TEST(OAuthRequestSignerTest, SignPost1) {
+ GURL request_url("https://www.google.com/accounts/o8/GetOAuthToken");
+ OAuthRequestSigner::Parameters parameters;
+ parameters["scope"] = "https://accounts.google.com/OAuthLogin";
+ parameters["oauth_nonce"] = "2oiE_aHdk5qRTz0L9C8Lq0g";
+ parameters["xaouth_display_name"] = "Chromium";
+ parameters["oauth_timestamp"] = "1308152953";
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::SignURL(
+ request_url,
+ parameters,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::POST_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/X8x0r7bHif_VNCLjUMutxGkzo13d", // oauth_token
+ "b7120598d47594bd3522", // token secret
+ &signed_text));
+ ASSERT_EQ("oauth_consumer_key=anonymous"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&oauth_signature=vVlfv6dnV2%2Fx7TozS0Gf83zS2%2BQ%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308152953"
+ "&oauth_token=4%2FX8x0r7bHif_VNCLjUMutxGkzo13d"
+ "&oauth_version=1.0"
+ "&scope=https%3A%2F%2Faccounts.google.com%2FOAuthLogin"
+ "&xaouth_display_name=Chromium",
+ signed_text);
+}
+
+TEST(OAuthRequestSignerTest, SignPost2) {
+ GURL request_url("https://accounts.google.com/OAuthGetAccessToken");
+ OAuthRequestSigner::Parameters parameters;
+ parameters["oauth_timestamp"] = "1234567890";
+ parameters["oauth_nonce"] = "17171717171717171";
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::SignURL(
+ request_url,
+ parameters,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::POST_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/CcC-hgdj1TNnWaX8NTQ76YDXCBEK", // oauth_token
+ std::string(), // token secret
+ &signed_text));
+ ASSERT_EQ(signed_text,
+ "oauth_consumer_key=anonymous"
+ "&oauth_nonce=17171717171717171"
+ "&oauth_signature=tPX2XqKQICWzopZ80CFGX%2F53DLo%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1234567890"
+ "&oauth_token=4%2FCcC-hgdj1TNnWaX8NTQ76YDXCBEK"
+ "&oauth_version=1.0");
+}
+
+TEST(OAuthRequestSignerTest, ParseAndSignPost1) {
+ GURL request_url("https://www.google.com/accounts/o8/GetOAuthToken"
+ "?scope=https://accounts.google.com/OAuthLogin"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&xaouth_display_name=Chromium"
+ "&oauth_timestamp=1308152953");
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::ParseAndSign(
+ request_url,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::POST_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/X8x0r7bHif_VNCLjUMutxGkzo13d", // oauth_token
+ "b7120598d47594bd3522", // token secret
+ &signed_text));
+ ASSERT_EQ("oauth_consumer_key=anonymous"
+ "&oauth_nonce=2oiE_aHdk5qRTz0L9C8Lq0g"
+ "&oauth_signature=vVlfv6dnV2%2Fx7TozS0Gf83zS2%2BQ%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1308152953"
+ "&oauth_token=4%2FX8x0r7bHif_VNCLjUMutxGkzo13d"
+ "&oauth_version=1.0"
+ "&scope=https%3A%2F%2Faccounts.google.com%2FOAuthLogin"
+ "&xaouth_display_name=Chromium",
+ signed_text);
+}
+
+TEST(OAuthRequestSignerTest, ParseAndSignPost2) {
+ GURL request_url("https://accounts.google.com/OAuthGetAccessToken"
+ "?oauth_timestamp=1234567890"
+ "&oauth_nonce=17171717171717171");
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::ParseAndSign(
+ request_url,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::POST_METHOD,
+ "anonymous", // oauth_consumer_key
+ "anonymous", // consumer secret
+ "4/CcC-hgdj1TNnWaX8NTQ76YDXCBEK", // oauth_token
+ std::string(), // token secret
+ &signed_text));
+ ASSERT_EQ(signed_text,
+ "oauth_consumer_key=anonymous"
+ "&oauth_nonce=17171717171717171"
+ "&oauth_signature=tPX2XqKQICWzopZ80CFGX%2F53DLo%3D"
+ "&oauth_signature_method=HMAC-SHA1"
+ "&oauth_timestamp=1234567890"
+ "&oauth_token=4%2FCcC-hgdj1TNnWaX8NTQ76YDXCBEK"
+ "&oauth_version=1.0");
+}
+
+TEST(OAuthRequestSignerTest, SignAuthHeader) {
+ GURL request_url("https://www.google.com/accounts/o8/GetOAuthToken");
+ OAuthRequestSigner::Parameters parameters;
+ parameters["scope"] = "https://accounts.google.com/OAuthLogin";
+ parameters["oauth_nonce"] = "2oiE_aHdk5qRTz0L9C8Lq0g";
+ parameters["xaouth_display_name"] = "Chromium";
+ parameters["oauth_timestamp"] = "1308152953";
+ std::string signed_text;
+ ASSERT_TRUE(OAuthRequestSigner::SignAuthHeader(
+ request_url,
+ parameters,
+ OAuthRequestSigner::HMAC_SHA1_SIGNATURE,
+ OAuthRequestSigner::GET_METHOD,
+ "johndoe", // oauth_consumer_key
+ "53cR3t", // consumer secret
+ "4/VGY0MsQadcmO8VnCv9gnhoEooq1v", // oauth_token
+ "c5e0531ff55dfbb4054e", // token secret
+ &signed_text));
+ ASSERT_EQ("OAuth "
+ "oauth_consumer_key=\"johndoe\", "
+ "oauth_nonce=\"2oiE_aHdk5qRTz0L9C8Lq0g\", "
+ "oauth_signature=\"PFqDTaiyey1UObcvOyI4Ng2HXW0%3D\", "
+ "oauth_signature_method=\"HMAC-SHA1\", "
+ "oauth_timestamp=\"1308152953\", "
+ "oauth_token=\"4%2FVGY0MsQadcmO8VnCv9gnhoEooq1v\", "
+ "oauth_version=\"1.0\", "
+ "scope=\"https%3A%2F%2Faccounts.google.com%2FOAuthLogin\", "
+ "xaouth_display_name=\"Chromium\"",
+ signed_text);
+}
diff --git a/chromium/google_apis/google_api_keys.cc b/chromium/google_apis/google_api_keys.cc
new file mode 100644
index 00000000000..fc4b54735b4
--- /dev/null
+++ b/chromium/google_apis/google_api_keys.cc
@@ -0,0 +1,249 @@
+// Copyright (c) 2012 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.
+
+#include "google_apis/google_api_keys.h"
+
+// If you add more includes to this list, you also need to add them to
+// google_api_keys_unittest.cc.
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringize_macros.h"
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_OFFICIAL_GOOGLE_API_KEYS)
+#include "google_apis/internal/google_chrome_api_keys.h"
+#endif
+
+// Used to indicate an unset key/id/secret. This works better with
+// various unit tests than leaving the token empty.
+#define DUMMY_API_TOKEN "dummytoken"
+
+#if !defined(GOOGLE_API_KEY)
+#define GOOGLE_API_KEY DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_ID_MAIN)
+#define GOOGLE_CLIENT_ID_MAIN DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_SECRET_MAIN)
+#define GOOGLE_CLIENT_SECRET_MAIN DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_ID_CLOUD_PRINT)
+#define GOOGLE_CLIENT_ID_CLOUD_PRINT DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_SECRET_CLOUD_PRINT)
+#define GOOGLE_CLIENT_SECRET_CLOUD_PRINT DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_ID_REMOTING)
+#define GOOGLE_CLIENT_ID_REMOTING DUMMY_API_TOKEN
+#endif
+
+#if !defined(GOOGLE_CLIENT_SECRET_REMOTING)
+#define GOOGLE_CLIENT_SECRET_REMOTING DUMMY_API_TOKEN
+#endif
+
+// These are used as shortcuts for developers and users providing
+// OAuth credentials via preprocessor defines or environment
+// variables. If set, they will be used to replace any of the client
+// IDs and secrets above that have not been set (and only those; they
+// will not override already-set values).
+#if !defined(GOOGLE_DEFAULT_CLIENT_ID)
+#define GOOGLE_DEFAULT_CLIENT_ID ""
+#endif
+#if !defined(GOOGLE_DEFAULT_CLIENT_SECRET)
+#define GOOGLE_DEFAULT_CLIENT_SECRET ""
+#endif
+
+namespace switches {
+
+// Specifies custom OAuth2 client id for testing purposes.
+const char kOAuth2ClientID[] = "oauth2-client-id";
+
+// Specifies custom OAuth2 client secret for testing purposes.
+const char kOAuth2ClientSecret[] = "oauth2-client-secret";
+
+} // namespace switches
+
+namespace google_apis {
+
+// This is used as a lazy instance to determine keys once and cache them.
+class APIKeyCache {
+ public:
+ APIKeyCache() {
+ scoped_ptr<base::Environment> environment(base::Environment::Create());
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+
+ api_key_ = CalculateKeyValue(GOOGLE_API_KEY,
+ STRINGIZE_NO_EXPANSION(GOOGLE_API_KEY),
+ NULL,
+ std::string(),
+ environment.get(),
+ command_line);
+
+ std::string default_client_id =
+ CalculateKeyValue(GOOGLE_DEFAULT_CLIENT_ID,
+ STRINGIZE_NO_EXPANSION(GOOGLE_DEFAULT_CLIENT_ID),
+ NULL,
+ std::string(),
+ environment.get(),
+ command_line);
+ std::string default_client_secret =
+ CalculateKeyValue(GOOGLE_DEFAULT_CLIENT_SECRET,
+ STRINGIZE_NO_EXPANSION(GOOGLE_DEFAULT_CLIENT_SECRET),
+ NULL,
+ std::string(),
+ environment.get(),
+ command_line);
+
+ // We currently only allow overriding the baked-in values for the
+ // default OAuth2 client ID and secret using a command-line
+ // argument, since that is useful to enable testing against
+ // staging servers, and since that was what was possible and
+ // likely practiced by the QA team before this implementation was
+ // written.
+ client_ids_[CLIENT_MAIN] = CalculateKeyValue(
+ GOOGLE_CLIENT_ID_MAIN,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_ID_MAIN),
+ switches::kOAuth2ClientID,
+ default_client_id,
+ environment.get(),
+ command_line);
+ client_secrets_[CLIENT_MAIN] = CalculateKeyValue(
+ GOOGLE_CLIENT_SECRET_MAIN,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_SECRET_MAIN),
+ switches::kOAuth2ClientSecret,
+ default_client_secret,
+ environment.get(),
+ command_line);
+
+ client_ids_[CLIENT_CLOUD_PRINT] = CalculateKeyValue(
+ GOOGLE_CLIENT_ID_CLOUD_PRINT,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_ID_CLOUD_PRINT),
+ NULL,
+ default_client_id,
+ environment.get(),
+ command_line);
+ client_secrets_[CLIENT_CLOUD_PRINT] = CalculateKeyValue(
+ GOOGLE_CLIENT_SECRET_CLOUD_PRINT,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_SECRET_CLOUD_PRINT),
+ NULL,
+ default_client_secret,
+ environment.get(),
+ command_line);
+
+ client_ids_[CLIENT_REMOTING] = CalculateKeyValue(
+ GOOGLE_CLIENT_ID_REMOTING,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_ID_REMOTING),
+ NULL,
+ default_client_id,
+ environment.get(),
+ command_line);
+ client_secrets_[CLIENT_REMOTING] = CalculateKeyValue(
+ GOOGLE_CLIENT_SECRET_REMOTING,
+ STRINGIZE_NO_EXPANSION(GOOGLE_CLIENT_SECRET_REMOTING),
+ NULL,
+ default_client_secret,
+ environment.get(),
+ command_line);
+ }
+
+ std::string api_key() const { return api_key_; }
+
+ std::string GetClientID(OAuth2Client client) const {
+ DCHECK_LT(client, CLIENT_NUM_ITEMS);
+ return client_ids_[client];
+ }
+
+ std::string GetClientSecret(OAuth2Client client) const {
+ DCHECK_LT(client, CLIENT_NUM_ITEMS);
+ return client_secrets_[client];
+ }
+
+ private:
+ // Gets a value for a key. In priority order, this will be the value
+ // provided via a command-line switch, the value provided via an
+ // environment variable, or finally a value baked into the build.
+ // |command_line_switch| may be NULL.
+ static std::string CalculateKeyValue(const char* baked_in_value,
+ const char* environment_variable_name,
+ const char* command_line_switch,
+ const std::string& default_if_unset,
+ base::Environment* environment,
+ CommandLine* command_line) {
+ std::string key_value = baked_in_value;
+ std::string temp;
+ if (environment->GetVar(environment_variable_name, &temp)) {
+ key_value = temp;
+ LOG(INFO) << "Overriding API key " << environment_variable_name
+ << " with value " << key_value << " from environment variable.";
+ }
+
+ if (command_line_switch && command_line->HasSwitch(command_line_switch)) {
+ key_value = command_line->GetSwitchValueASCII(command_line_switch);
+ LOG(INFO) << "Overriding API key " << environment_variable_name
+ << " with value " << key_value << " from command-line switch.";
+ }
+
+ if (key_value == DUMMY_API_TOKEN) {
+#if defined(GOOGLE_CHROME_BUILD)
+ // No key should be unset in an official build except the
+ // GOOGLE_DEFAULT_* keys. The default keys don't trigger this
+ // check as their "unset" value is not DUMMY_API_TOKEN.
+ CHECK(false);
+#endif
+ if (default_if_unset.size() > 0) {
+ LOG(INFO) << "Using default value \"" << default_if_unset
+ << "\" for API key " << environment_variable_name;
+ key_value = default_if_unset;
+ }
+ }
+
+ // This should remain a debug-only log.
+ DVLOG(1) << "API key " << environment_variable_name << "=" << key_value;
+
+ return key_value;
+ }
+
+ std::string api_key_;
+ std::string client_ids_[CLIENT_NUM_ITEMS];
+ std::string client_secrets_[CLIENT_NUM_ITEMS];
+};
+
+static base::LazyInstance<APIKeyCache> g_api_key_cache =
+ LAZY_INSTANCE_INITIALIZER;
+
+bool HasKeysConfigured() {
+ if (GetAPIKey() == DUMMY_API_TOKEN)
+ return false;
+
+ for (size_t client_id = 0; client_id < CLIENT_NUM_ITEMS; ++client_id) {
+ OAuth2Client client = static_cast<OAuth2Client>(client_id);
+ if (GetOAuth2ClientID(client) == DUMMY_API_TOKEN ||
+ GetOAuth2ClientSecret(client) == DUMMY_API_TOKEN) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+std::string GetAPIKey() {
+ return g_api_key_cache.Get().api_key();
+}
+
+std::string GetOAuth2ClientID(OAuth2Client client) {
+ return g_api_key_cache.Get().GetClientID(client);
+}
+
+std::string GetOAuth2ClientSecret(OAuth2Client client) {
+ return g_api_key_cache.Get().GetClientSecret(client);
+}
+
+} // namespace google_apis
diff --git a/chromium/google_apis/google_api_keys.h b/chromium/google_apis/google_api_keys.h
new file mode 100644
index 00000000000..28421e8e222
--- /dev/null
+++ b/chromium/google_apis/google_api_keys.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2012 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.
+
+#ifndef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#define GOOGLE_APIS_GOOGLE_API_KEYS_H_
+
+// If you add more includes to this file, you also need to add them to
+// google_api_keys_unittest.cc.
+#include <string>
+
+// These functions enable you to retrieve keys to use for Google APIs
+// such as Translate and Safe Browsing.
+//
+// You can retrieve either an "API key" (sometimes called a developer
+// key) which identifies you (or the company you work for) as a
+// developer, or you can retrieve the "client ID" and "client secret"
+// used by you (or the company you work for) to generate OAuth2
+// requests.
+//
+// Each developer (or group of developers working together for a
+// single company) must request a Google API key and the client ID and
+// client secret for OAuth2. See
+// https://developers.google.com/console/help/ and
+// https://developers.google.com/console/.
+//
+// The keys must either be provided using preprocessor variables (set
+// via e.g. ~/.gyp/include.gypi). Alternatively, they can be
+// overridden at runtime using environment variables of the same name.
+//
+// The names of the preprocessor variables (or environment variables
+// to override them at runtime) are as follows:
+// - GOOGLE_API_KEY: The API key, a.k.a. developer key.
+// - GOOGLE_DEFAULT_CLIENT_ID: If set, this is used as the default for
+// all client IDs not otherwise set. This is intended only for
+// development.
+// - GOOGLE_DEFAULT_CLIENT_SECRET: If set, this is used as the default
+// for all client secrets. This is intended only for development.
+// - GOOGLE_CLIENT_ID_[client name]
+// (e.g. GOOGLE_CLIENT_ID_CLOUD_PRINT, i.e. one for each item in the
+// OAuth2Client enumeration below)
+// - GOOGLE_CLIENT_SECRET_[client name]
+// (e.g. GOOGLE_CLIENT_SECRET_CLOUD_PRINT, i.e. one for each item in
+// the OAuth2Client enumeration below)
+//
+// The GOOGLE_CLIENT_ID_MAIN and GOOGLE_CLIENT_SECRET_MAIN values can
+// also be set via the command line (this overrides any other
+// setting). The command-line parameters are --oauth2-client-id and
+// --oauth2-client-secret.
+//
+// If some of the parameters mentioned above are not provided,
+// Chromium will still build and run, but services that require them
+// may fail to work without warning. They should do so gracefully,
+// similar to what would happen when a network connection is
+// unavailable.
+
+namespace google_apis {
+
+// Returns true if no dummy API keys or OAuth2 tokens are set.
+bool HasKeysConfigured();
+
+// Retrieves the API key, a.k.a. developer key, or a dummy string
+// if not set.
+//
+// Note that the key should be escaped for the context you use it in,
+// e.g. URL-escaped if you use it in a URL.
+std::string GetAPIKey();
+
+// Represents the different sets of client IDs and secrets in use.
+enum OAuth2Client {
+ CLIENT_MAIN, // Several different features use this.
+ CLIENT_CLOUD_PRINT,
+ CLIENT_REMOTING,
+
+ CLIENT_NUM_ITEMS // Must be last item.
+};
+
+// Retrieves the OAuth2 client ID for the specified client, or the
+// empty string if not set.
+//
+// Note that the ID should be escaped for the context you use it in,
+// e.g. URL-escaped if you use it in a URL.
+std::string GetOAuth2ClientID(OAuth2Client client);
+
+// Retrieves the OAuth2 client secret for the specified client, or the
+// empty string if not set.
+//
+// Note that the secret should be escaped for the context you use it
+// in, e.g. URL-escaped if you use it in a URL.
+std::string GetOAuth2ClientSecret(OAuth2Client client);
+
+} // namespace google_apis
+
+#endif // GOOGLE_APIS_GOOGLE_API_KEYS_H_
diff --git a/chromium/google_apis/google_api_keys.py b/chromium/google_apis/google_api_keys.py
new file mode 100755
index 00000000000..6776aaec343
--- /dev/null
+++ b/chromium/google_apis/google_api_keys.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Python API for retrieving API keys.
+
+Note that this does not have the exact same semantics as the C++ API
+in google_api_keys.h, since it does not have access to gyp variables
+or preprocessor defines.
+"""
+
+import os
+import re
+import sys
+
+
+# The token returned when an API key is unset.
+DUMMY_TOKEN = 'dummytoken'
+
+
+def _GetTokenFromOfficialFile(token_name):
+ """Parses the token from the official file if it exists, else returns None."""
+ official_path = os.path.join(os.path.dirname(__file__),
+ 'internal/google_chrome_api_keys.h')
+ if not os.path.isfile(official_path):
+ return None
+
+ line_regexp = '^#define\s*%s\s*"([^"]+)"' % token_name
+ line_pattern = re.compile(line_regexp)
+ def ParseLine(current_line):
+ result = line_pattern.match(current_line)
+ if result:
+ return result.group(1)
+ else:
+ return None
+
+ with open(official_path) as f:
+ current_line = ''
+ for line in f:
+ if line.endswith('\\\n'):
+ current_line += line[:-2]
+ else:
+ # Last line in multi-line #define, or a line that is not a
+ # continuation line.
+ current_line += line
+ token = ParseLine(current_line)
+ if token:
+ if current_line.count('"') != 2:
+ raise Exception(
+ 'Embedded quotes and multi-line strings are not supported.')
+ return token
+ current_line = ''
+ return None
+
+
+def _GetToken(token_name):
+ """Returns the API token with the given name, or DUMMY_TOKEN by default."""
+ if token_name in os.environ:
+ return os.environ[token_name]
+ token = _GetTokenFromOfficialFile(token_name)
+ if token:
+ return token
+ else:
+ return DUMMY_TOKEN
+
+
+def GetAPIKey():
+ """Returns the simple API key."""
+ return _GetToken('GOOGLE_API_KEY')
+
+
+def GetClientID(client_name):
+ """Returns the OAuth 2.0 client ID for the client of the given name."""
+ return _GetToken('GOOGLE_CLIENT_ID_%s' % client_name)
+
+
+def GetClientSecret(client_name):
+ """Returns the OAuth 2.0 client secret for the client of the given name."""
+ return _GetToken('GOOGLE_CLIENT_SECRET_%s' % client_name)
+
+
+if __name__ == "__main__":
+ print 'GOOGLE_API_KEY=%s' % GetAPIKey()
+ print 'GOOGLE_CLIENT_ID_MAIN=%s' % GetClientID('MAIN')
+ print 'GOOGLE_CLIENT_SECRET_MAIN=%s' % GetClientSecret('MAIN')
+ print 'GOOGLE_CLIENT_ID_CLOUD_PRINT=%s' % GetClientID('CLOUD_PRINT')
+ print 'GOOGLE_CLIENT_SECRET_CLOUD_PRINT=%s' % GetClientSecret('CLOUD_PRINT')
+ print 'GOOGLE_CLIENT_ID_REMOTING=%s' % GetClientID('REMOTING')
+ print 'GOOGLE_CLIENT_SECRET_REMOTING=%s' % GetClientSecret('REMOTING')
diff --git a/chromium/google_apis/google_api_keys_unittest.cc b/chromium/google_apis/google_api_keys_unittest.cc
new file mode 100644
index 00000000000..4d338f5289b
--- /dev/null
+++ b/chromium/google_apis/google_api_keys_unittest.cc
@@ -0,0 +1,438 @@
+// Copyright (c) 2012 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.
+
+// Unit tests for implementation of google_api_keys namespace.
+//
+// Because the file deals with a lot of preprocessor defines and
+// optionally includes an internal header, the way we test is by
+// including the .cc file multiple times with different defines set.
+// This is a little unorthodox, but it lets us test the behavior as
+// close to unmodified as possible.
+
+#include "google_apis/google_api_keys.h"
+
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// The Win builders fail (with a linker crash) when trying to link
+// unit_tests, and the Android builders complain about multiply
+// defined symbols (likely they don't do name decoration as well as
+// the Mac and Linux linkers). Therefore these tests are only built
+// and run on Mac and Linux, which should provide plenty of coverage
+// since there are no platform-specific bits in this code.
+#if defined(OS_LINUX) || defined(OS_MACOSX)
+
+// We need to include everything included by google_api_keys.cc once
+// at global scope so that things like STL and classes from base don't
+// get defined when we re-include the google_api_keys.cc file
+// below. We used to include that file in its entirety here, but that
+// can cause problems if the linker decides the version of symbols
+// from that file included here is the "right" version.
+#include <string>
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringize_macros.h"
+
+// This is the default baked-in value for OAuth IDs and secrets.
+static const char kDummyToken[] = "dummytoken";
+
+struct EnvironmentCache {
+ public:
+ EnvironmentCache() : variable_name(NULL), was_set(false) {}
+
+ const char* variable_name;
+ bool was_set;
+ std::string value;
+};
+
+class GoogleAPIKeysTest : public testing::Test {
+ public:
+ GoogleAPIKeysTest() : env_(base::Environment::Create()) {
+ env_cache_[0].variable_name = "GOOGLE_API_KEY";
+ env_cache_[1].variable_name = "GOOGLE_CLIENT_ID_MAIN";
+ env_cache_[2].variable_name = "GOOGLE_CLIENT_SECRET_MAIN";
+ env_cache_[3].variable_name = "GOOGLE_CLIENT_ID_CLOUD_PRINT";
+ env_cache_[4].variable_name = "GOOGLE_CLIENT_SECRET_CLOUD_PRINT";
+ env_cache_[5].variable_name = "GOOGLE_CLIENT_ID_REMOTING";
+ env_cache_[6].variable_name = "GOOGLE_CLIENT_SECRET_REMOTING";
+ env_cache_[7].variable_name = "GOOGLE_DEFAULT_CLIENT_ID";
+ env_cache_[8].variable_name = "GOOGLE_DEFAULT_CLIENT_SECRET";
+ }
+
+ virtual void SetUp() {
+ // Unset all environment variables that can affect these tests,
+ // for the duration of the tests.
+ for (size_t i = 0; i < arraysize(env_cache_); ++i) {
+ EnvironmentCache& cache = env_cache_[i];
+ cache.was_set = env_->HasVar(cache.variable_name);
+ cache.value.clear();
+ if (cache.was_set) {
+ env_->GetVar(cache.variable_name, &cache.value);
+ env_->UnSetVar(cache.variable_name);
+ }
+ }
+ }
+
+ virtual void TearDown() {
+ // Restore environment.
+ for (size_t i = 0; i < arraysize(env_cache_); ++i) {
+ EnvironmentCache& cache = env_cache_[i];
+ if (cache.was_set) {
+ env_->SetVar(cache.variable_name, cache.value);
+ }
+ }
+ }
+
+ private:
+ scoped_ptr<base::Environment> env_;
+
+ // Why 3? It is for GOOGLE_API_KEY, GOOGLE_DEFAULT_CLIENT_ID and
+ // GOOGLE_DEFAULT_CLIENT_SECRET.
+ //
+ // Why 2 times CLIENT_NUM_ITEMS? This is the number of different
+ // clients in the OAuth2Client enumeration, and for each of these we
+ // have both an ID and a secret.
+ EnvironmentCache env_cache_[3 + 2 * google_apis::CLIENT_NUM_ITEMS];
+};
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_OFFICIAL_GOOGLE_API_KEYS)
+// Test official build behavior, since we are in a checkout where this
+// is possible.
+namespace official_build {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in google_api_keys.cc
+#undef DUMMY_API_TOKEN
+#undef GOOGLE_API_KEY
+#undef GOOGLE_CLIENT_ID_MAIN
+#undef GOOGLE_CLIENT_SECRET_MAIN
+#undef GOOGLE_CLIENT_ID_CLOUD_PRINT
+#undef GOOGLE_CLIENT_SECRET_CLOUD_PRINT
+#undef GOOGLE_CLIENT_ID_REMOTING
+#undef GOOGLE_CLIENT_SECRET_REMOTING
+#undef GOOGLE_DEFAULT_CLIENT_ID
+#undef GOOGLE_DEFAULT_CLIENT_SECRET
+
+// Try setting some keys, these should be ignored since it's a build
+// with official keys.
+#define GOOGLE_API_KEY "bogus api_key"
+#define GOOGLE_CLIENT_ID_MAIN "bogus client_id_main"
+
+// Undef include guard so things get defined again, within this namespace.
+#undef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#undef GOOGLE_APIS_INTERNAL_GOOGLE_CHROME_API_KEYS_
+#include "google_apis/google_api_keys.cc"
+
+} // namespace official_build
+
+TEST_F(GoogleAPIKeysTest, OfficialKeys) {
+ namespace testcase = official_build::google_apis;
+
+ EXPECT_TRUE(testcase::HasKeysConfigured());
+
+ std::string api_key = testcase::g_api_key_cache.Get().api_key();
+ std::string id_main = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_MAIN);
+ std::string secret_main = testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_MAIN);
+ std::string id_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string secret_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string id_remoting = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_REMOTING);
+ std::string secret_remoting =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_REMOTING);
+
+ EXPECT_NE(0u, api_key.size());
+ EXPECT_NE(DUMMY_API_TOKEN, api_key);
+ EXPECT_NE("bogus api_key", api_key);
+ EXPECT_NE(kDummyToken, api_key);
+
+ EXPECT_NE(0u, id_main.size());
+ EXPECT_NE(DUMMY_API_TOKEN, id_main);
+ EXPECT_NE("bogus client_id_main", id_main);
+ EXPECT_NE(kDummyToken, id_main);
+
+ EXPECT_NE(0u, secret_main.size());
+ EXPECT_NE(DUMMY_API_TOKEN, secret_main);
+ EXPECT_NE(kDummyToken, secret_main);
+
+ EXPECT_NE(0u, id_cloud_print.size());
+ EXPECT_NE(DUMMY_API_TOKEN, id_cloud_print);
+ EXPECT_NE(kDummyToken, id_cloud_print);
+
+ EXPECT_NE(0u, secret_cloud_print.size());
+ EXPECT_NE(DUMMY_API_TOKEN, secret_cloud_print);
+ EXPECT_NE(kDummyToken, secret_cloud_print);
+
+ EXPECT_NE(0u, id_remoting.size());
+ EXPECT_NE(DUMMY_API_TOKEN, id_remoting);
+ EXPECT_NE(kDummyToken, id_remoting);
+
+ EXPECT_NE(0u, secret_remoting.size());
+ EXPECT_NE(DUMMY_API_TOKEN, secret_remoting);
+ EXPECT_NE(kDummyToken, secret_remoting);
+}
+#endif // defined(GOOGLE_CHROME_BUILD) || defined(USE_OFFICIAL_GOOGLE_API_KEYS)
+
+// After this test, for the remainder of this compilation unit, we
+// need official keys to not be used.
+#undef GOOGLE_CHROME_BUILD
+#undef USE_OFFICIAL_GOOGLE_API_KEYS
+
+// Test the set of keys temporarily baked into Chromium by default.
+namespace default_keys {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in google_api_keys.cc
+#undef DUMMY_API_TOKEN
+#undef GOOGLE_API_KEY
+#undef GOOGLE_CLIENT_ID_MAIN
+#undef GOOGLE_CLIENT_SECRET_MAIN
+#undef GOOGLE_CLIENT_ID_CLOUD_PRINT
+#undef GOOGLE_CLIENT_SECRET_CLOUD_PRINT
+#undef GOOGLE_CLIENT_ID_REMOTING
+#undef GOOGLE_CLIENT_SECRET_REMOTING
+#undef GOOGLE_DEFAULT_CLIENT_ID
+#undef GOOGLE_DEFAULT_CLIENT_SECRET
+
+// Undef include guard so things get defined again, within this namespace.
+#undef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#undef GOOGLE_APIS_INTERNAL_GOOGLE_CHROME_API_KEYS_
+#include "google_apis/google_api_keys.cc"
+
+} // namespace default_keys
+
+TEST_F(GoogleAPIKeysTest, DefaultKeys) {
+ namespace testcase = default_keys::google_apis;
+
+ EXPECT_FALSE(testcase::HasKeysConfigured());
+
+ std::string api_key = testcase::g_api_key_cache.Get().api_key();
+ std::string id_main = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_MAIN);
+ std::string secret_main = testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_MAIN);
+ std::string id_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string secret_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string id_remoting = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_REMOTING);
+ std::string secret_remoting =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_REMOTING);
+
+ EXPECT_EQ(kDummyToken, api_key);
+ EXPECT_EQ(kDummyToken, id_main);
+ EXPECT_EQ(kDummyToken, secret_main);
+ EXPECT_EQ(kDummyToken, id_cloud_print);
+ EXPECT_EQ(kDummyToken, secret_cloud_print);
+ EXPECT_EQ(kDummyToken, id_remoting);
+ EXPECT_EQ(kDummyToken, secret_remoting);
+}
+
+// Override a couple of keys, leave the rest default.
+namespace override_some_keys {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in google_api_keys.cc
+#undef DUMMY_API_TOKEN
+#undef GOOGLE_API_KEY
+#undef GOOGLE_CLIENT_ID_MAIN
+#undef GOOGLE_CLIENT_SECRET_MAIN
+#undef GOOGLE_CLIENT_ID_CLOUD_PRINT
+#undef GOOGLE_CLIENT_SECRET_CLOUD_PRINT
+#undef GOOGLE_CLIENT_ID_REMOTING
+#undef GOOGLE_CLIENT_SECRET_REMOTING
+#undef GOOGLE_DEFAULT_CLIENT_ID
+#undef GOOGLE_DEFAULT_CLIENT_SECRET
+
+#define GOOGLE_API_KEY "API_KEY override"
+#define GOOGLE_CLIENT_ID_REMOTING "CLIENT_ID_REMOTING override"
+
+// Undef include guard so things get defined again, within this namespace.
+#undef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#undef GOOGLE_APIS_INTERNAL_GOOGLE_CHROME_API_KEYS_
+#include "google_apis/google_api_keys.cc"
+
+} // namespace override_some_keys
+
+TEST_F(GoogleAPIKeysTest, OverrideSomeKeys) {
+ namespace testcase = override_some_keys::google_apis;
+
+ EXPECT_FALSE(testcase::HasKeysConfigured());
+
+ std::string api_key = testcase::g_api_key_cache.Get().api_key();
+ std::string id_main = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_MAIN);
+ std::string secret_main = testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_MAIN);
+ std::string id_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string secret_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string id_remoting = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_REMOTING);
+ std::string secret_remoting =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_REMOTING);
+
+ EXPECT_EQ("API_KEY override", api_key);
+ EXPECT_EQ(kDummyToken, id_main);
+ EXPECT_EQ(kDummyToken, secret_main);
+ EXPECT_EQ(kDummyToken, id_cloud_print);
+ EXPECT_EQ(kDummyToken, secret_cloud_print);
+ EXPECT_EQ("CLIENT_ID_REMOTING override", id_remoting);
+ EXPECT_EQ(kDummyToken, secret_remoting);
+}
+
+// Override all keys.
+namespace override_all_keys {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in google_api_keys.cc
+#undef DUMMY_API_TOKEN
+#undef GOOGLE_API_KEY
+#undef GOOGLE_CLIENT_ID_MAIN
+#undef GOOGLE_CLIENT_SECRET_MAIN
+#undef GOOGLE_CLIENT_ID_CLOUD_PRINT
+#undef GOOGLE_CLIENT_SECRET_CLOUD_PRINT
+#undef GOOGLE_CLIENT_ID_REMOTING
+#undef GOOGLE_CLIENT_SECRET_REMOTING
+#undef GOOGLE_DEFAULT_CLIENT_ID
+#undef GOOGLE_DEFAULT_CLIENT_SECRET
+
+#define GOOGLE_API_KEY "API_KEY"
+#define GOOGLE_CLIENT_ID_MAIN "ID_MAIN"
+#define GOOGLE_CLIENT_SECRET_MAIN "SECRET_MAIN"
+#define GOOGLE_CLIENT_ID_CLOUD_PRINT "ID_CLOUD_PRINT"
+#define GOOGLE_CLIENT_SECRET_CLOUD_PRINT "SECRET_CLOUD_PRINT"
+#define GOOGLE_CLIENT_ID_REMOTING "ID_REMOTING"
+#define GOOGLE_CLIENT_SECRET_REMOTING "SECRET_REMOTING"
+
+// Undef include guard so things get defined again, within this namespace.
+#undef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#undef GOOGLE_APIS_INTERNAL_GOOGLE_CHROME_API_KEYS_
+#include "google_apis/google_api_keys.cc"
+
+} // namespace override_all_keys
+
+TEST_F(GoogleAPIKeysTest, OverrideAllKeys) {
+ namespace testcase = override_all_keys::google_apis;
+
+ EXPECT_TRUE(testcase::HasKeysConfigured());
+
+ std::string api_key = testcase::g_api_key_cache.Get().api_key();
+ std::string id_main = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_MAIN);
+ std::string secret_main = testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_MAIN);
+ std::string id_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string secret_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string id_remoting = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_REMOTING);
+ std::string secret_remoting =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_REMOTING);
+
+ EXPECT_EQ("API_KEY", api_key);
+ EXPECT_EQ("ID_MAIN", id_main);
+ EXPECT_EQ("SECRET_MAIN", secret_main);
+ EXPECT_EQ("ID_CLOUD_PRINT", id_cloud_print);
+ EXPECT_EQ("SECRET_CLOUD_PRINT", secret_cloud_print);
+ EXPECT_EQ("ID_REMOTING", id_remoting);
+ EXPECT_EQ("SECRET_REMOTING", secret_remoting);
+}
+
+// Override all keys using both preprocessor defines and environment
+// variables. The environment variables should win.
+namespace override_all_keys_env {
+
+// We start every test by creating a clean environment for the
+// preprocessor defines used in google_api_keys.cc
+#undef DUMMY_API_TOKEN
+#undef GOOGLE_API_KEY
+#undef GOOGLE_CLIENT_ID_MAIN
+#undef GOOGLE_CLIENT_SECRET_MAIN
+#undef GOOGLE_CLIENT_ID_CLOUD_PRINT
+#undef GOOGLE_CLIENT_SECRET_CLOUD_PRINT
+#undef GOOGLE_CLIENT_ID_REMOTING
+#undef GOOGLE_CLIENT_SECRET_REMOTING
+#undef GOOGLE_DEFAULT_CLIENT_ID
+#undef GOOGLE_DEFAULT_CLIENT_SECRET
+
+#define GOOGLE_API_KEY "API_KEY"
+#define GOOGLE_CLIENT_ID_MAIN "ID_MAIN"
+#define GOOGLE_CLIENT_SECRET_MAIN "SECRET_MAIN"
+#define GOOGLE_CLIENT_ID_CLOUD_PRINT "ID_CLOUD_PRINT"
+#define GOOGLE_CLIENT_SECRET_CLOUD_PRINT "SECRET_CLOUD_PRINT"
+#define GOOGLE_CLIENT_ID_REMOTING "ID_REMOTING"
+#define GOOGLE_CLIENT_SECRET_REMOTING "SECRET_REMOTING"
+
+// Undef include guard so things get defined again, within this namespace.
+#undef GOOGLE_APIS_GOOGLE_API_KEYS_H_
+#undef GOOGLE_APIS_INTERNAL_GOOGLE_CHROME_API_KEYS_
+#include "google_apis/google_api_keys.cc"
+
+} // namespace override_all_keys_env
+
+TEST_F(GoogleAPIKeysTest, OverrideAllKeysUsingEnvironment) {
+ namespace testcase = override_all_keys_env::google_apis;
+
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ env->SetVar("GOOGLE_API_KEY", "env-API_KEY");
+ env->SetVar("GOOGLE_CLIENT_ID_MAIN", "env-ID_MAIN");
+ env->SetVar("GOOGLE_CLIENT_ID_CLOUD_PRINT", "env-ID_CLOUD_PRINT");
+ env->SetVar("GOOGLE_CLIENT_ID_REMOTING", "env-ID_REMOTING");
+ env->SetVar("GOOGLE_CLIENT_SECRET_MAIN", "env-SECRET_MAIN");
+ env->SetVar("GOOGLE_CLIENT_SECRET_CLOUD_PRINT", "env-SECRET_CLOUD_PRINT");
+ env->SetVar("GOOGLE_CLIENT_SECRET_REMOTING", "env-SECRET_REMOTING");
+
+ EXPECT_TRUE(testcase::HasKeysConfigured());
+
+ // It's important that the first call to Get() only happen after the
+ // environment variables have been set.
+ std::string api_key = testcase::g_api_key_cache.Get().api_key();
+ std::string id_main = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_MAIN);
+ std::string secret_main = testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_MAIN);
+ std::string id_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string secret_cloud_print =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_CLOUD_PRINT);
+ std::string id_remoting = testcase::g_api_key_cache.Get().GetClientID(
+ testcase::CLIENT_REMOTING);
+ std::string secret_remoting =
+ testcase::g_api_key_cache.Get().GetClientSecret(
+ testcase::CLIENT_REMOTING);
+
+ EXPECT_EQ("env-API_KEY", api_key);
+ EXPECT_EQ("env-ID_MAIN", id_main);
+ EXPECT_EQ("env-SECRET_MAIN", secret_main);
+ EXPECT_EQ("env-ID_CLOUD_PRINT", id_cloud_print);
+ EXPECT_EQ("env-SECRET_CLOUD_PRINT", secret_cloud_print);
+ EXPECT_EQ("env-ID_REMOTING", id_remoting);
+ EXPECT_EQ("env-SECRET_REMOTING", secret_remoting);
+}
+
+#endif // defined(OS_LINUX) || defined(OS_MACOSX)
diff --git a/chromium/google_apis/google_apis.gyp b/chromium/google_apis/google_apis.gyp
new file mode 100644
index 00000000000..c67232ffb5f
--- /dev/null
+++ b/chromium/google_apis/google_apis.gyp
@@ -0,0 +1,93 @@
+# Copyright (c) 2012 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1, # Use higher warning level.
+ },
+ 'includes': [
+ '../build/win_precompile.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'google_apis',
+ 'type': 'static_library',
+ 'includes': [
+ 'determine_use_official_keys.gypi',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ '../crypto/crypto.gyp:crypto',
+ '../net/net.gyp:net',
+ ],
+ 'conditions': [
+ ['google_api_key!=""', {
+ 'defines': ['GOOGLE_API_KEY="<(google_api_key)"'],
+ }],
+ ['google_default_client_id!=""', {
+ 'defines': [
+ 'GOOGLE_DEFAULT_CLIENT_ID="<(google_default_client_id)"',
+ ]
+ }],
+ ['google_default_client_secret!=""', {
+ 'defines': [
+ 'GOOGLE_DEFAULT_CLIENT_SECRET="<(google_default_client_secret)"',
+ ]
+ }],
+ [ 'OS == "android"', {
+ 'dependencies': [
+ '../third_party/openssl/openssl.gyp:openssl',
+ ],
+ 'sources/': [
+ ['exclude', 'cup/client_update_protocol_nss\.cc$'],
+ ],
+ }],
+ [ 'use_openssl==1', {
+ 'sources!': [
+ 'cup/client_update_protocol_nss.cc',
+ ],
+ }, {
+ 'sources!': [
+ 'cup/client_update_protocol_openssl.cc',
+ ],
+ },],
+ ],
+ 'sources': [
+ 'cup/client_update_protocol.cc',
+ 'cup/client_update_protocol.h',
+ 'cup/client_update_protocol_nss.cc',
+ 'cup/client_update_protocol_openssl.cc',
+ 'gaia/gaia_auth_consumer.cc',
+ 'gaia/gaia_auth_consumer.h',
+ 'gaia/gaia_auth_fetcher.cc',
+ 'gaia/gaia_auth_fetcher.h',
+ 'gaia/gaia_auth_util.cc',
+ 'gaia/gaia_auth_util.h',
+ 'gaia/gaia_constants.cc',
+ 'gaia/gaia_constants.h',
+ 'gaia/gaia_oauth_client.cc',
+ 'gaia/gaia_oauth_client.h',
+ 'gaia/gaia_switches.cc',
+ 'gaia/gaia_switches.h',
+ 'gaia/gaia_urls.cc',
+ 'gaia/gaia_urls.h',
+ 'gaia/google_service_auth_error.cc',
+ 'gaia/google_service_auth_error.h',
+ 'gaia/oauth_request_signer.cc',
+ 'gaia/oauth_request_signer.h',
+ 'gaia/oauth2_access_token_consumer.h',
+ 'gaia/oauth2_access_token_fetcher.cc',
+ 'gaia/oauth2_access_token_fetcher.h',
+ 'gaia/oauth2_api_call_flow.cc',
+ 'gaia/oauth2_api_call_flow.h',
+ 'gaia/oauth2_mint_token_flow.cc',
+ 'gaia/oauth2_mint_token_flow.h',
+ 'google_api_keys.cc',
+ 'google_api_keys.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [4267, ],
+ },
+ ],
+}