1// Copyright 2015 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#import "ios/chrome/browser/ui/authentication/authentication_flow.h"
6
7#include <memory>
8
9#include "base/bind.h"
10#include "base/memory/ptr_util.h"
11#import "base/test/ios/wait_util.h"
12#include "components/pref_registry/pref_registry_syncable.h"
13#include "components/sync_preferences/pref_service_mock_factory.h"
14#include "components/sync_preferences/pref_service_syncable.h"
15#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
16#include "ios/chrome/browser/main/test_browser.h"
17#include "ios/chrome/browser/prefs/browser_prefs.h"
18#include "ios/chrome/browser/signin/authentication_service_factory.h"
19#import "ios/chrome/browser/signin/authentication_service_fake.h"
20#import "ios/chrome/browser/ui/authentication/authentication_flow_performer.h"
21#include "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service.h"
22#include "ios/web/public/test/web_task_environment.h"
23#import "testing/gtest_mac.h"
24#import "testing/platform_test.h"
25#import "third_party/ocmock/gtest_support.h"
26#import "third_party/ocmock/ocmock_extensions.h"
27#include "ui/base/l10n/l10n_util.h"
28
29#if !defined(__has_feature) || !__has_feature(objc_arc)
30#error "This file requires ARC support."
31#endif
32
33namespace {
34
35class AuthenticationFlowTest : public PlatformTest {
36 protected:
37  void SetUp() override {
38    PlatformTest::SetUp();
39
40    TestChromeBrowserState::Builder builder;
41    builder.AddTestingFactory(
42        AuthenticationServiceFactory::GetInstance(),
43        base::BindRepeating(
44            &AuthenticationServiceFake::CreateAuthenticationService));
45    builder.SetPrefService(CreatePrefService());
46    browser_state_ = builder.Build();
47    WebStateList* web_state_list = nullptr;
48    browser_ =
49        std::make_unique<TestBrowser>(browser_state_.get(), web_state_list);
50
51    ios::FakeChromeIdentityService* identityService =
52        ios::FakeChromeIdentityService::GetInstanceFromChromeProvider();
53    identityService->AddIdentities(@[ @"identity1", @"identity2" ]);
54    identity1_ =
55        [identityService->GetAllIdentitiesSortedForDisplay() objectAtIndex:0];
56    identity2_ =
57        [identityService->GetAllIdentitiesSortedForDisplay() objectAtIndex:1];
58    sign_in_completion_ = ^(BOOL success) {
59      finished_ = true;
60      signed_in_success_ = success;
61    };
62    finished_ = false;
63    signed_in_success_ = false;
64  }
65
66  std::unique_ptr<sync_preferences::PrefServiceSyncable> CreatePrefService() {
67    sync_preferences::PrefServiceMockFactory factory;
68    scoped_refptr<user_prefs::PrefRegistrySyncable> registry(
69        new user_prefs::PrefRegistrySyncable);
70    std::unique_ptr<sync_preferences::PrefServiceSyncable> prefs =
71        factory.CreateSyncable(registry.get());
72    RegisterBrowserStatePrefs(registry.get());
73    return prefs;
74  }
75
76  AuthenticationFlowPerformer* GetAuthenticationFlowPerformer() {
77    return static_cast<AuthenticationFlowPerformer*>(performer_);
78  }
79
80  // Creates a new AuthenticationFlow with default values for fields that are
81  // not directly useful.
82  void CreateAuthenticationFlow(ShouldClearData shouldClearData,
83                                PostSignInAction postSignInAction) {
84    ChromeIdentity* identity = identity1_;
85    view_controller_ = [OCMockObject niceMockForClass:[UIViewController class]];
86    authentication_flow_ =
87        [[AuthenticationFlow alloc] initWithBrowser:browser_.get()
88                                           identity:identity
89                                    shouldClearData:shouldClearData
90                                   postSignInAction:postSignInAction
91                           presentingViewController:view_controller_];
92    performer_ =
93        [OCMockObject mockForClass:[AuthenticationFlowPerformer class]];
94    [authentication_flow_
95        setPerformerForTesting:GetAuthenticationFlowPerformer()];
96  }
97
98  // Checks if the AuthenticationFlow operation has completed, and whether it
99  // was successful.
100  void CheckSignInCompletion(bool expectedSignedIn) {
101    base::test::ios::WaitUntilCondition(^bool {
102      return finished_;
103    });
104    EXPECT_EQ(true, finished_);
105    EXPECT_EQ(expectedSignedIn, signed_in_success_);
106    [performer_ verify];
107  }
108
109  web::WebTaskEnvironment task_environment_;
110  AuthenticationFlow* authentication_flow_;
111  std::unique_ptr<TestChromeBrowserState> browser_state_;
112  std::unique_ptr<Browser> browser_;
113  ChromeIdentity* identity1_;
114  ChromeIdentity* identity2_;
115  OCMockObject* performer_;
116  signin_ui::CompletionCallback sign_in_completion_;
117  UIViewController* view_controller_;
118
119  // State of the flow
120  bool finished_;
121  bool signed_in_success_;
122};
123
124// Tests a Sign In of a normal account on the same profile, merging user data
125// and showing the sync settings.
126TEST_F(AuthenticationFlowTest, TestSignInSimple) {
127  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_MERGE_DATA,
128                           POST_SIGNIN_ACTION_START_SYNC);
129
130  [[[performer_ expect] andDo:^(NSInvocation*) {
131    [authentication_flow_ didFetchManagedStatus:nil];
132  }] fetchManagedStatus:browser_state_.get()
133             forIdentity:identity1_];
134
135  [[[performer_ expect] andReturnBool:NO]
136      shouldHandleMergeCaseForIdentity:identity1_
137                          browserState:browser_state_.get()];
138
139  [[performer_ expect] signInIdentity:identity1_
140                     withHostedDomain:nil
141                       toBrowserState:browser_state_.get()];
142
143  [[performer_ expect] commitSyncForBrowserState:browser_state_.get()];
144
145  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
146
147  CheckSignInCompletion(true);
148}
149
150// Tests that signing in an already signed in account correctly signs it out
151// and back in.
152TEST_F(AuthenticationFlowTest, TestAlreadySignedIn) {
153  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_MERGE_DATA,
154                           POST_SIGNIN_ACTION_NONE);
155
156  [[[performer_ expect] andDo:^(NSInvocation*) {
157    [authentication_flow_ didFetchManagedStatus:nil];
158  }] fetchManagedStatus:browser_state_.get()
159             forIdentity:identity1_];
160
161  [[[performer_ expect] andReturnBool:NO]
162      shouldHandleMergeCaseForIdentity:identity1_
163                          browserState:browser_state_.get()];
164
165  [[[performer_ expect] andDo:^(NSInvocation*) {
166    [authentication_flow_ didSignOut];
167  }] signOutBrowserState:browser_state_.get()];
168
169  [[performer_ expect] signInIdentity:identity1_
170                     withHostedDomain:nil
171                       toBrowserState:browser_state_.get()];
172
173  AuthenticationServiceFactory::GetForBrowserState(browser_state_.get())
174      ->SignIn(identity1_);
175  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
176
177  CheckSignInCompletion(true);
178}
179
180// Tests a Sign In of a different account, requiring a sign out of the already
181// signed in account, and asking the user whether data should be cleared or
182// merged.
183TEST_F(AuthenticationFlowTest, TestSignOutUserChoice) {
184  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_USER_CHOICE,
185                           POST_SIGNIN_ACTION_START_SYNC);
186
187  [[[performer_ expect] andDo:^(NSInvocation*) {
188    [authentication_flow_ didFetchManagedStatus:nil];
189  }] fetchManagedStatus:browser_state_.get()
190             forIdentity:identity1_];
191
192  [[[performer_ expect] andReturnBool:YES]
193      shouldHandleMergeCaseForIdentity:identity1_
194                          browserState:browser_state_.get()];
195
196  [[[performer_ expect] andDo:^(NSInvocation*) {
197    [authentication_flow_
198        didChooseClearDataPolicy:SHOULD_CLEAR_DATA_CLEAR_DATA];
199  }] promptMergeCaseForIdentity:identity1_
200                        browser:browser_.get()
201                 viewController:view_controller_];
202
203  [[[performer_ expect] andDo:^(NSInvocation*) {
204    [authentication_flow_ didSignOut];
205  }] signOutBrowserState:browser_state_.get()];
206
207  [[[performer_ expect] andDo:^(NSInvocation*) {
208    [authentication_flow_ didClearData];
209  }] clearDataFromBrowser:browser_.get() commandHandler:nil];
210
211  [[performer_ expect] signInIdentity:identity1_
212                     withHostedDomain:nil
213                       toBrowserState:browser_state_.get()];
214
215  [[performer_ expect] commitSyncForBrowserState:browser_state_.get()];
216
217  AuthenticationServiceFactory::GetForBrowserState(browser_state_.get())
218      ->SignIn(identity2_);
219  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
220
221  CheckSignInCompletion(true);
222}
223
224// Tests the cancelling of a Sign In.
225TEST_F(AuthenticationFlowTest, TestCancel) {
226  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_USER_CHOICE,
227                           POST_SIGNIN_ACTION_START_SYNC);
228
229  [[[performer_ expect] andDo:^(NSInvocation*) {
230    [authentication_flow_ didFetchManagedStatus:nil];
231  }] fetchManagedStatus:browser_state_.get()
232             forIdentity:identity1_];
233
234  [[[performer_ expect] andReturnBool:YES]
235      shouldHandleMergeCaseForIdentity:identity1_
236                          browserState:browser_state_.get()];
237
238  [[[performer_ expect] andDo:^(NSInvocation*) {
239    [authentication_flow_ cancelAndDismissAnimated:NO];
240  }] promptMergeCaseForIdentity:identity1_
241                        browser:browser_.get()
242                 viewController:view_controller_];
243
244  [[performer_ expect] cancelAndDismissAnimated:NO];
245
246  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
247
248  CheckSignInCompletion(false);
249}
250
251// Tests the fetch managed status failure case.
252TEST_F(AuthenticationFlowTest, TestFailFetchManagedStatus) {
253  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_MERGE_DATA,
254                           POST_SIGNIN_ACTION_START_SYNC);
255
256  NSError* error = [NSError errorWithDomain:@"foo" code:0 userInfo:nil];
257  [[[performer_ expect] andDo:^(NSInvocation*) {
258    [authentication_flow_ didFailFetchManagedStatus:error];
259  }] fetchManagedStatus:browser_state_.get()
260             forIdentity:identity1_];
261
262  [[[performer_ expect] andDo:^(NSInvocation* invocation) {
263    __unsafe_unretained ProceduralBlock completionBlock;
264    [invocation getArgument:&completionBlock atIndex:3];
265    completionBlock();
266  }] showAuthenticationError:[OCMArg any]
267              withCompletion:[OCMArg any]
268              viewController:view_controller_
269                     browser:browser_.get()];
270
271  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
272
273  CheckSignInCompletion(false);
274}
275
276// Tests the managed sign in confirmation dialog is shown when signing in to
277// a managed identity.
278TEST_F(AuthenticationFlowTest, TestShowManagedConfirmation) {
279  CreateAuthenticationFlow(SHOULD_CLEAR_DATA_CLEAR_DATA,
280                           POST_SIGNIN_ACTION_START_SYNC);
281
282  [[[performer_ expect] andDo:^(NSInvocation*) {
283    [authentication_flow_ didFetchManagedStatus:@"foo.com"];
284  }] fetchManagedStatus:browser_state_.get()
285             forIdentity:identity1_];
286
287  [[[performer_ expect] andReturnBool:NO]
288      shouldHandleMergeCaseForIdentity:identity1_
289                          browserState:browser_state_.get()];
290
291  [[[performer_ expect] andDo:^(NSInvocation*) {
292    [authentication_flow_ didAcceptManagedConfirmation];
293  }] showManagedConfirmationForHostedDomain:@"foo.com"
294                             viewController:view_controller_
295                                    browser:browser_.get()];
296
297  [[[performer_ expect] andDo:^(NSInvocation*) {
298    [authentication_flow_ didClearData];
299  }] clearDataFromBrowser:browser_.get() commandHandler:nil];
300
301  [[performer_ expect] signInIdentity:identity1_
302                     withHostedDomain:@"foo.com"
303                       toBrowserState:browser_state_.get()];
304
305  [[performer_ expect] commitSyncForBrowserState:browser_state_.get()];
306
307  [authentication_flow_ startSignInWithCompletion:sign_in_completion_];
308
309  CheckSignInCompletion(true);
310}
311
312}  // namespace
313