1// Copyright (c) 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/extensions/api/identity/gaia_web_auth_flow.h"
6
7#include <vector>
8
9#include "base/message_loop/message_loop.h"
10#include "base/run_loop.h"
11#include "content/public/test/test_browser_thread.h"
12#include "testing/gmock/include/gmock/gmock.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace extensions {
16
17class FakeWebAuthFlow : public WebAuthFlow {
18 public:
19  explicit FakeWebAuthFlow(WebAuthFlow::Delegate* delegate)
20      : WebAuthFlow(delegate,
21                    NULL,
22                    GURL(),
23                    WebAuthFlow::INTERACTIVE) {}
24
25  virtual void Start() OVERRIDE {}
26};
27
28class TestGaiaWebAuthFlow : public GaiaWebAuthFlow {
29 public:
30  TestGaiaWebAuthFlow(GaiaWebAuthFlow::Delegate* delegate,
31                      const ExtensionTokenKey* token_key,
32                      const std::string oauth2_client_id,
33                      GoogleServiceAuthError::State ubertoken_error_state)
34      : GaiaWebAuthFlow(delegate, NULL, token_key, oauth2_client_id, "en-us"),
35        ubertoken_error_(ubertoken_error_state) {}
36
37  virtual void Start() OVERRIDE {
38    if (ubertoken_error_.state() == GoogleServiceAuthError::NONE)
39      OnUbertokenSuccess("fake_ubertoken");
40    else
41      OnUbertokenFailure(ubertoken_error_);
42  }
43
44 private:
45  virtual scoped_ptr<WebAuthFlow> CreateWebAuthFlow(GURL url) OVERRIDE {
46    return scoped_ptr<WebAuthFlow>(new FakeWebAuthFlow(this));
47  }
48
49  GoogleServiceAuthError ubertoken_error_;
50};
51
52class MockGaiaWebAuthFlowDelegate : public GaiaWebAuthFlow::Delegate {
53 public:
54  MOCK_METHOD3(OnGaiaFlowFailure,
55               void(GaiaWebAuthFlow::Failure failure,
56                    GoogleServiceAuthError service_error,
57                    const std::string& oauth_error));
58  MOCK_METHOD2(OnGaiaFlowCompleted,
59               void(const std::string& access_token,
60                    const std::string& expiration));
61};
62
63class IdentityGaiaWebAuthFlowTest : public testing::Test {
64 public:
65  IdentityGaiaWebAuthFlowTest()
66      : ubertoken_error_state_(GoogleServiceAuthError::NONE),
67        fake_ui_thread_(content::BrowserThread::UI, &message_loop_) {}
68
69  virtual void TearDown() {
70    testing::Test::TearDown();
71    base::RunLoop loop;
72    loop.RunUntilIdle();  // Run tasks so FakeWebAuthFlows get deleted.
73  }
74
75  scoped_ptr<TestGaiaWebAuthFlow> CreateTestFlow() {
76    ExtensionTokenKey token_key(
77        "extension_id", "account_id", std::set<std::string>());
78    return scoped_ptr<TestGaiaWebAuthFlow>(new TestGaiaWebAuthFlow(
79        &delegate_, &token_key, "fake.client.id", ubertoken_error_state_));
80  }
81
82  std::string GetFinalTitle(const std::string& fragment) {
83    return std::string("Loading id.client.fake:/extension_id#") + fragment;
84  }
85
86  GoogleServiceAuthError GetNoneServiceError() {
87    return GoogleServiceAuthError(GoogleServiceAuthError::NONE);
88  }
89
90  void set_ubertoken_error(
91      GoogleServiceAuthError::State ubertoken_error_state) {
92    ubertoken_error_state_ = ubertoken_error_state;
93  }
94
95 protected:
96  testing::StrictMock<MockGaiaWebAuthFlowDelegate> delegate_;
97  GoogleServiceAuthError::State ubertoken_error_state_;
98  base::MessageLoop message_loop_;
99  content::TestBrowserThread fake_ui_thread_;
100};
101
102TEST_F(IdentityGaiaWebAuthFlowTest, OAuthError) {
103  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
104  flow->Start();
105  EXPECT_CALL(delegate_, OnGaiaFlowFailure(
106          GaiaWebAuthFlow::OAUTH_ERROR,
107          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
108          "access_denied"));
109  flow->OnAuthFlowTitleChange(GetFinalTitle("error=access_denied"));
110}
111
112TEST_F(IdentityGaiaWebAuthFlowTest, Token) {
113  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
114  flow->Start();
115  EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", ""));
116  flow->OnAuthFlowTitleChange(GetFinalTitle("access_token=fake_access_token"));
117}
118
119TEST_F(IdentityGaiaWebAuthFlowTest, TokenAndExpiration) {
120  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
121  flow->Start();
122  EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", "3600"));
123  flow->OnAuthFlowTitleChange(
124      GetFinalTitle("access_token=fake_access_token&expires_in=3600"));
125}
126
127TEST_F(IdentityGaiaWebAuthFlowTest, ExtraFragmentParametersSuccess) {
128  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
129  flow->Start();
130  EXPECT_CALL(delegate_,
131              OnGaiaFlowCompleted("fake_access_token", "3600"));
132  flow->OnAuthFlowTitleChange(GetFinalTitle("chaff1=stuff&"
133                                            "expires_in=3600&"
134                                            "chaff2=and&"
135                                            "nonerror=fake_error&"
136                                            "chaff3=nonsense&"
137                                            "access_token=fake_access_token&"
138                                            "chaff4="));
139}
140
141TEST_F(IdentityGaiaWebAuthFlowTest, ExtraFragmentParametersError) {
142  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
143  flow->Start();
144  EXPECT_CALL(delegate_, OnGaiaFlowFailure(
145          GaiaWebAuthFlow::OAUTH_ERROR,
146          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
147          "fake_error"));
148  flow->OnAuthFlowTitleChange(GetFinalTitle("chaff1=stuff&"
149                                            "expires_in=3600&"
150                                            "chaff2=and&"
151                                            "error=fake_error&"
152                                            "chaff3=nonsense&"
153                                            "access_token=fake_access_token&"
154                                            "chaff4="));
155}
156
157TEST_F(IdentityGaiaWebAuthFlowTest, TitleSpam) {
158  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
159  flow->Start();
160  flow->OnAuthFlowTitleChange(
161      "Loading https://extension_id.chromiumapp.org/#error=non_final_title");
162  flow->OnAuthFlowTitleChange("I'm feeling entitled.");
163  flow->OnAuthFlowTitleChange("");
164  flow->OnAuthFlowTitleChange(
165      "Loading id.client.fake:/bad_extension_id#error=non_final_title");
166  flow->OnAuthFlowTitleChange(
167      "Loading bad.id.client.fake:/extension_id#error=non_final_title");
168  EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", ""));
169  flow->OnAuthFlowTitleChange(GetFinalTitle("access_token=fake_access_token"));
170}
171
172TEST_F(IdentityGaiaWebAuthFlowTest, EmptyFragment) {
173  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
174  flow->Start();
175  EXPECT_CALL(
176      delegate_,
177      OnGaiaFlowFailure(
178          GaiaWebAuthFlow::INVALID_REDIRECT,
179          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
180          ""));
181  flow->OnAuthFlowTitleChange(GetFinalTitle(""));
182}
183
184TEST_F(IdentityGaiaWebAuthFlowTest, JunkFragment) {
185  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
186  flow->Start();
187  EXPECT_CALL(
188      delegate_,
189      OnGaiaFlowFailure(
190          GaiaWebAuthFlow::INVALID_REDIRECT,
191          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
192          ""));
193  flow->OnAuthFlowTitleChange(GetFinalTitle("thisisjustabunchofjunk"));
194}
195
196TEST_F(IdentityGaiaWebAuthFlowTest, NoFragment) {
197  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
198  flow->Start();
199  // This won't be recognized as an interesting title.
200  flow->OnAuthFlowTitleChange("Loading id.client.fake:/extension_id");
201}
202
203TEST_F(IdentityGaiaWebAuthFlowTest, Host) {
204  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
205  flow->Start();
206  // These won't be recognized as interesting titles.
207  flow->OnAuthFlowTitleChange(
208      "Loading id.client.fake://extension_id#access_token=fake_access_token");
209  flow->OnAuthFlowTitleChange(
210      "Loading id.client.fake://extension_id/#access_token=fake_access_token");
211  flow->OnAuthFlowTitleChange(
212      "Loading "
213      "id.client.fake://host/extension_id/#access_token=fake_access_token");
214}
215
216TEST_F(IdentityGaiaWebAuthFlowTest, UbertokenFailure) {
217  set_ubertoken_error(GoogleServiceAuthError::CONNECTION_FAILED);
218  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
219  EXPECT_CALL(
220      delegate_,
221      OnGaiaFlowFailure(
222          GaiaWebAuthFlow::SERVICE_AUTH_ERROR,
223          GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED),
224          ""));
225  flow->Start();
226}
227
228TEST_F(IdentityGaiaWebAuthFlowTest, AuthFlowFailure) {
229  scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
230  flow->Start();
231  EXPECT_CALL(
232      delegate_,
233      OnGaiaFlowFailure(
234          GaiaWebAuthFlow::WINDOW_CLOSED,
235          GoogleServiceAuthError(GoogleServiceAuthError::NONE),
236          ""));
237  flow->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
238}
239
240}  // namespace extensions
241