Home | History | Annotate | Download | only in settings
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "base/prefs/testing_pref_service.h"
      9 #include "base/run_loop.h"
     10 #include "chrome/browser/signin/oauth2_token_service_test_util.h"
     11 #include "chrome/common/pref_names.h"
     12 #include "chrome/test/base/scoped_testing_local_state.h"
     13 #include "chrome/test/base/testing_browser_process.h"
     14 #include "chromeos/cryptohome/mock_cryptohome_library.h"
     15 #include "content/public/browser/browser_thread.h"
     16 #include "content/public/test/test_browser_thread.h"
     17 #include "google_apis/gaia/gaia_oauth_client.h"
     18 #include "net/http/http_status_code.h"
     19 #include "net/url_request/test_url_fetcher_factory.h"
     20 #include "net/url_request/url_fetcher_delegate.h"
     21 #include "net/url_request/url_request_test_util.h"
     22 #include "testing/gmock/include/gmock/gmock.h"
     23 #include "testing/gtest/include/gtest/gtest.h"
     24 
     25 using ::testing::_;
     26 using ::testing::AnyNumber;
     27 using ::testing::Return;
     28 using ::testing::StrEq;
     29 using ::testing::StrictMock;
     30 
     31 namespace chromeos {
     32 
     33 static const int kOAuthTokenServiceUrlFetcherId = 0;
     34 static const int kValidatorUrlFetcherId = gaia::GaiaOAuthClient::kUrlFetcherId;
     35 
     36 class TestDeviceOAuth2TokenService : public DeviceOAuth2TokenService {
     37  public:
     38   explicit TestDeviceOAuth2TokenService(net::URLRequestContextGetter* getter,
     39                                         PrefService* local_state)
     40       : DeviceOAuth2TokenService(getter, local_state) {
     41   }
     42   void SetRobotAccountIdPolicyValue(const std::string& id) {
     43     robot_account_id_ = id;
     44   }
     45 
     46  protected:
     47   // Skip calling into the policy subsystem and return our test value.
     48   virtual std::string GetRobotAccountId() OVERRIDE {
     49     return robot_account_id_;
     50   }
     51 
     52  private:
     53   std::string robot_account_id_;
     54   DISALLOW_COPY_AND_ASSIGN(TestDeviceOAuth2TokenService);
     55 };
     56 
     57 class DeviceOAuth2TokenServiceTest : public testing::Test {
     58  public:
     59   DeviceOAuth2TokenServiceTest()
     60       : ui_thread_(content::BrowserThread::UI, &message_loop_),
     61         scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
     62         request_context_getter_(new net::TestURLRequestContextGetter(
     63             message_loop_.message_loop_proxy())),
     64         oauth2_service_(request_context_getter_.get(),
     65                         scoped_testing_local_state_.Get()) {
     66     oauth2_service_.max_refresh_token_validation_retries_ = 0;
     67     oauth2_service_.set_max_authorization_token_fetch_retries_for_testing(0);
     68   }
     69   virtual ~DeviceOAuth2TokenServiceTest() {}
     70 
     71   // Most tests just want a noop crypto impl with a dummy refresh token value in
     72   // Local State (if the value is an empty string, it will be ignored).
     73   void SetUpDefaultValues() {
     74     cryptohome_library_.reset(chromeos::CryptohomeLibrary::GetTestImpl());
     75     chromeos::CryptohomeLibrary::SetForTest(cryptohome_library_.get());
     76     SetDeviceRefreshTokenInLocalState("device_refresh_token_4_test");
     77     oauth2_service_.SetRobotAccountIdPolicyValue("service_acct (at) g.com");
     78     AssertConsumerTokensAndErrors(0, 0);
     79   }
     80 
     81   scoped_ptr<OAuth2TokenService::Request> StartTokenRequest() {
     82     return oauth2_service_.StartRequest(std::set<std::string>(), &consumer_);
     83   }
     84 
     85   virtual void TearDown() OVERRIDE {
     86     CryptohomeLibrary::SetForTest(NULL);
     87     base::RunLoop().RunUntilIdle();
     88   }
     89 
     90   // Utility method to set a value in Local State for the device refresh token
     91   // (it must have a non-empty value or it won't be used).
     92   void SetDeviceRefreshTokenInLocalState(const std::string& refresh_token) {
     93     scoped_testing_local_state_.Get()->SetManagedPref(
     94         prefs::kDeviceRobotAnyApiRefreshToken,
     95         Value::CreateStringValue(refresh_token));
     96   }
     97 
     98   std::string GetValidTokenInfoResponse(const std::string email) {
     99     return "{ \"email\": \"" + email + "\","
    100            "  \"user_id\": \"1234567890\" }";
    101   }
    102 
    103   // A utility method to return fake URL results, for testing the refresh token
    104   // validation logic.  For a successful validation attempt, this method will be
    105   // called three times for the steps listed below (steps 1 and 2 happen in
    106   // parallel).
    107   //
    108   // Step 1a: fetch the access token for the tokeninfo API.
    109   // Step 1b: call the tokeninfo API.
    110   // Step 2:  Fetch the access token for the requested scope
    111   //          (in this case, cloudprint).
    112   void ReturnOAuthUrlFetchResults(int fetcher_id,
    113                                   net::HttpStatusCode response_code,
    114                                   const std::string&  response_string);
    115 
    116   void AssertConsumerTokensAndErrors(int num_tokens, int num_errors);
    117 
    118  protected:
    119   base::MessageLoop message_loop_;
    120   content::TestBrowserThread ui_thread_;
    121   ScopedTestingLocalState scoped_testing_local_state_;
    122   scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
    123   net::TestURLFetcherFactory factory_;
    124   TestDeviceOAuth2TokenService oauth2_service_;
    125   TestingOAuth2TokenServiceConsumer consumer_;
    126   scoped_ptr<chromeos::CryptohomeLibrary> cryptohome_library_;
    127 
    128 };
    129 
    130 void DeviceOAuth2TokenServiceTest::ReturnOAuthUrlFetchResults(
    131     int fetcher_id,
    132     net::HttpStatusCode response_code,
    133     const std::string&  response_string) {
    134 
    135   net::TestURLFetcher* fetcher = factory_.GetFetcherByID(fetcher_id);
    136   ASSERT_TRUE(fetcher);
    137   fetcher->set_response_code(response_code);
    138   fetcher->SetResponseString(response_string);
    139   fetcher->delegate()->OnURLFetchComplete(fetcher);
    140 }
    141 
    142 void DeviceOAuth2TokenServiceTest::AssertConsumerTokensAndErrors(
    143     int num_tokens,
    144     int num_errors) {
    145   ASSERT_EQ(num_tokens, consumer_.number_of_successful_tokens_);
    146   ASSERT_EQ(num_errors, consumer_.number_of_errors_);
    147 }
    148 
    149 TEST_F(DeviceOAuth2TokenServiceTest, SaveEncryptedToken) {
    150   StrictMock<MockCryptohomeLibrary> mock_cryptohome_library;
    151   CryptohomeLibrary::SetForTest(&mock_cryptohome_library);
    152 
    153   EXPECT_CALL(mock_cryptohome_library, DecryptWithSystemSalt(StrEq("")))
    154       .Times(1)
    155       .WillOnce(Return(""));
    156   EXPECT_CALL(mock_cryptohome_library,
    157               EncryptWithSystemSalt(StrEq("test-token")))
    158       .Times(1)
    159       .WillOnce(Return("encrypted"));
    160   EXPECT_CALL(mock_cryptohome_library,
    161               DecryptWithSystemSalt(StrEq("encrypted")))
    162       .Times(1)
    163       .WillOnce(Return("test-token"));
    164 
    165   ASSERT_EQ("", oauth2_service_.GetRefreshToken());
    166   oauth2_service_.SetAndSaveRefreshToken("test-token");
    167   ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken());
    168 
    169   // This call won't invoke decrypt again, since the value is cached.
    170   ASSERT_EQ("test-token", oauth2_service_.GetRefreshToken());
    171 }
    172 
    173 TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Success) {
    174   SetUpDefaultValues();
    175   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    176 
    177   ReturnOAuthUrlFetchResults(
    178       kValidatorUrlFetcherId,
    179       net::HTTP_OK,
    180       GetValidTokenResponse("tokeninfo_access_token", 3600));
    181 
    182   ReturnOAuthUrlFetchResults(
    183       kValidatorUrlFetcherId,
    184       net::HTTP_OK,
    185       GetValidTokenInfoResponse("service_acct (at) g.com"));
    186 
    187   ReturnOAuthUrlFetchResults(
    188       kOAuthTokenServiceUrlFetcherId,
    189       net::HTTP_OK,
    190       GetValidTokenResponse("scoped_access_token", 3600));
    191 
    192   AssertConsumerTokensAndErrors(1, 0);
    193 
    194   EXPECT_EQ("scoped_access_token", consumer_.last_token_);
    195 }
    196 
    197 TEST_F(DeviceOAuth2TokenServiceTest,
    198        RefreshTokenValidation_Failure_TokenInfoAccessTokenHttpError) {
    199   SetUpDefaultValues();
    200   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    201 
    202   ReturnOAuthUrlFetchResults(
    203       kValidatorUrlFetcherId,
    204       net::HTTP_UNAUTHORIZED,
    205       "");
    206 
    207   // TokenInfo API call skipped (error returned in previous step).
    208 
    209   // CloudPrint access token fetch is successful, but consumer still given error
    210   // due to bad refresh token.
    211   ReturnOAuthUrlFetchResults(
    212       kOAuthTokenServiceUrlFetcherId,
    213       net::HTTP_OK,
    214       GetValidTokenResponse("ignored_scoped_access_token", 3600));
    215 
    216   AssertConsumerTokensAndErrors(0, 1);
    217 }
    218 
    219 TEST_F(DeviceOAuth2TokenServiceTest,
    220        RefreshTokenValidation_Failure_TokenInfoAccessTokenInvalidResponse) {
    221   SetUpDefaultValues();
    222   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    223 
    224   ReturnOAuthUrlFetchResults(
    225       kValidatorUrlFetcherId,
    226       net::HTTP_OK,
    227       "invalid response");
    228 
    229   // TokenInfo API call skipped (error returned in previous step).
    230 
    231   ReturnOAuthUrlFetchResults(
    232       kOAuthTokenServiceUrlFetcherId,
    233       net::HTTP_OK,
    234       GetValidTokenResponse("ignored_scoped_access_token", 3600));
    235 
    236   // CloudPrint access token fetch is successful, but consumer still given error
    237   // due to bad refresh token.
    238   AssertConsumerTokensAndErrors(0, 1);
    239 }
    240 
    241 TEST_F(DeviceOAuth2TokenServiceTest,
    242        RefreshTokenValidation_Failure_TokenInfoApiCallHttpError) {
    243   SetUpDefaultValues();
    244   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    245 
    246   ReturnOAuthUrlFetchResults(
    247       kValidatorUrlFetcherId,
    248       net::HTTP_OK,
    249       GetValidTokenResponse("tokeninfo_access_token", 3600));
    250 
    251   ReturnOAuthUrlFetchResults(
    252       kValidatorUrlFetcherId,
    253       net::HTTP_INTERNAL_SERVER_ERROR,
    254       "");
    255 
    256   ReturnOAuthUrlFetchResults(
    257       kOAuthTokenServiceUrlFetcherId,
    258       net::HTTP_OK,
    259       GetValidTokenResponse("ignored_scoped_access_token", 3600));
    260 
    261   // CloudPrint access token fetch is successful, but consumer still given error
    262   // due to bad refresh token.
    263   AssertConsumerTokensAndErrors(0, 1);
    264 }
    265 
    266 TEST_F(DeviceOAuth2TokenServiceTest,
    267        RefreshTokenValidation_Failure_TokenInfoApiCallInvalidResponse) {
    268   SetUpDefaultValues();
    269   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    270 
    271   ReturnOAuthUrlFetchResults(
    272       kValidatorUrlFetcherId,
    273       net::HTTP_OK,
    274       GetValidTokenResponse("tokeninfo_access_token", 3600));
    275 
    276   ReturnOAuthUrlFetchResults(
    277       kValidatorUrlFetcherId,
    278       net::HTTP_OK,
    279       "invalid response");
    280 
    281   ReturnOAuthUrlFetchResults(
    282       kOAuthTokenServiceUrlFetcherId,
    283       net::HTTP_OK,
    284       GetValidTokenResponse("ignored_scoped_access_token", 3600));
    285 
    286   // CloudPrint access token fetch is successful, but consumer still given error
    287   // due to bad refresh token.
    288   AssertConsumerTokensAndErrors(0, 1);
    289 }
    290 
    291 TEST_F(DeviceOAuth2TokenServiceTest,
    292        RefreshTokenValidation_Failure_CloudPrintAccessTokenHttpError) {
    293   SetUpDefaultValues();
    294   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    295 
    296   ReturnOAuthUrlFetchResults(
    297       kValidatorUrlFetcherId,
    298       net::HTTP_OK,
    299       GetValidTokenResponse("tokeninfo_access_token", 3600));
    300 
    301   ReturnOAuthUrlFetchResults(
    302       kValidatorUrlFetcherId,
    303       net::HTTP_OK,
    304       GetValidTokenInfoResponse("service_acct (at) g.com"));
    305 
    306   ReturnOAuthUrlFetchResults(
    307       kOAuthTokenServiceUrlFetcherId,
    308       net::HTTP_BAD_REQUEST,
    309       "");
    310 
    311   AssertConsumerTokensAndErrors(0, 1);
    312 }
    313 
    314 TEST_F(DeviceOAuth2TokenServiceTest,
    315        RefreshTokenValidation_Failure_CloudPrintAccessTokenInvalidResponse) {
    316   SetUpDefaultValues();
    317   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    318 
    319   ReturnOAuthUrlFetchResults(
    320       kValidatorUrlFetcherId,
    321       net::HTTP_OK,
    322       GetValidTokenResponse("tokeninfo_access_token", 3600));
    323 
    324   ReturnOAuthUrlFetchResults(
    325       kValidatorUrlFetcherId,
    326       net::HTTP_OK,
    327       GetValidTokenInfoResponse("service_acct (at) g.com"));
    328 
    329   ReturnOAuthUrlFetchResults(
    330       kOAuthTokenServiceUrlFetcherId,
    331       net::HTTP_OK,
    332       "invalid request");
    333 
    334   AssertConsumerTokensAndErrors(0, 1);
    335 }
    336 
    337 TEST_F(DeviceOAuth2TokenServiceTest, RefreshTokenValidation_Failure_BadOwner) {
    338   SetUpDefaultValues();
    339   scoped_ptr<OAuth2TokenService::Request> request = StartTokenRequest();
    340 
    341   oauth2_service_.SetRobotAccountIdPolicyValue("WRONG_service_acct (at) g.com");
    342 
    343   // The requested token comes in before any of the validation calls complete,
    344   // but the consumer still gets an error, since the results don't get returned
    345   // until validation is over.
    346   ReturnOAuthUrlFetchResults(
    347       kOAuthTokenServiceUrlFetcherId,
    348       net::HTTP_OK,
    349       GetValidTokenResponse("ignored_scoped_access_token", 3600));
    350   AssertConsumerTokensAndErrors(0, 0);
    351 
    352   ReturnOAuthUrlFetchResults(
    353       kValidatorUrlFetcherId,
    354       net::HTTP_OK,
    355       GetValidTokenResponse("tokeninfo_access_token", 3600));
    356   AssertConsumerTokensAndErrors(0, 0);
    357 
    358   ReturnOAuthUrlFetchResults(
    359       kValidatorUrlFetcherId,
    360       net::HTTP_OK,
    361       GetValidTokenInfoResponse("service_acct (at) g.com"));
    362 
    363   // All fetches were successful, but consumer still given error since
    364   // the token owner doesn't match the policy value.
    365   AssertConsumerTokensAndErrors(0, 1);
    366 }
    367 
    368 }  // namespace chromeos
    369