blob: c05fcad7dc64cc80cd290d1baaad9b9629c92130 [file] [log] [blame]
// Copyright 2016 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.
#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service.h"
#import <Foundation/Foundation.h>
#include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h"
#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_interaction_manager.h"
#import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_service_constants.h"
#include "ios/public/provider/chrome/browser/signin/signin_resources_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using ::testing::_;
using ::testing::Invoke;
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace {
UIImage* FakeGetCachedAvatarForIdentity(ChromeIdentity*) {
ios::SigninResourcesProvider* provider =
ios::GetChromeBrowserProvider().GetSigninResourcesProvider();
return provider ? provider->GetDefaultAvatar() : nil;
}
NSString* FakeGetHostedDomainForIdentity(ChromeIdentity* identity) {
return base::SysUTF8ToNSString(gaia::ExtractDomainName(
gaia::CanonicalizeEmail(base::SysNSStringToUTF8(identity.userEmail))));
}
}
@interface FakeAccountDetailsViewController : UIViewController {
__weak ChromeIdentity* _identity;
UIButton* _removeAccountButton;
UIButton* _closeAccountDetailsButton;
}
@end
@implementation FakeAccountDetailsViewController
- (instancetype)initWithIdentity:(ChromeIdentity*)identity {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_identity = identity;
}
return self;
}
- (void)dealloc {
[_removeAccountButton removeTarget:self
action:@selector(didTapRemoveAccount:)
forControlEvents:UIControlEventTouchUpInside];
[_closeAccountDetailsButton removeTarget:self
action:@selector(didTapCloseAccount:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Obnoxious color, this is a test screen.
self.view.backgroundColor = [UIColor orangeColor];
_removeAccountButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self addButtonToSubviewWithTitle:@"Remove account"
button:_removeAccountButton
action:@selector(didTapRemoveAccount:)];
_closeAccountDetailsButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self addButtonToSubviewWithTitle:@"Close account"
button:_closeAccountDetailsButton
action:@selector(didTapCloseAccount:)];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
CGRect bounds = self.view.bounds;
[self sizeButtonToFitWithCenter:CGPointMake(CGRectGetMidX(bounds),
CGRectGetMinY(bounds))
button:_removeAccountButton];
[self sizeButtonToFitWithCenter:CGPointMake(CGRectGetMidX(bounds),
CGRectGetMidY(bounds))
button:_closeAccountDetailsButton];
}
#pragma mark - Private
- (void)addButtonToSubviewWithTitle:(NSString*)title
button:(UIButton*)button
action:(SEL)action {
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self
action:action
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)sizeButtonToFitWithCenter:(CGPoint)center button:(UIButton*)button {
[button setCenter:center];
[button sizeToFit];
}
- (void)didTapRemoveAccount:(id)sender {
ios::FakeChromeIdentityService::GetInstanceFromChromeProvider()
->ForgetIdentity(_identity, ^(NSError*) {
[self dismissViewControllerAnimated:YES completion:nil];
});
}
- (void)didTapCloseAccount:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
namespace ios {
NSString* const kIdentityEmailFormat = @"%@@gmail.com";
NSString* const kIdentityGaiaIDFormat = @"%@ID";
FakeChromeIdentityService::FakeChromeIdentityService()
: identities_([[NSMutableArray alloc] init]),
capabilitiesByIdentity_([[NSMutableDictionary alloc] init]),
_fakeMDMError(false),
_pendingCallback(0) {}
FakeChromeIdentityService::~FakeChromeIdentityService() {}
// static
FakeChromeIdentityService*
FakeChromeIdentityService::GetInstanceFromChromeProvider() {
return static_cast<ios::FakeChromeIdentityService*>(
ios::GetChromeBrowserProvider().GetChromeIdentityService());
}
DismissASMViewControllerBlock
FakeChromeIdentityService::PresentAccountDetailsController(
ChromeIdentity* identity,
UIViewController* viewController,
BOOL animated) {
UIViewController* accountDetailsViewController =
[[FakeAccountDetailsViewController alloc] initWithIdentity:identity];
[viewController presentViewController:accountDetailsViewController
animated:animated
completion:nil];
return ^(BOOL animated) {
[accountDetailsViewController dismissViewControllerAnimated:animated
completion:nil];
};
}
ChromeIdentityInteractionManager*
FakeChromeIdentityService::CreateChromeIdentityInteractionManager(
id<ChromeIdentityInteractionManagerDelegate> delegate) const {
return CreateFakeChromeIdentityInteractionManager(delegate);
}
FakeChromeIdentityInteractionManager*
FakeChromeIdentityService::CreateFakeChromeIdentityInteractionManager(
id<ChromeIdentityInteractionManagerDelegate> delegate) const {
FakeChromeIdentityInteractionManager* manager =
[[FakeChromeIdentityInteractionManager alloc] init];
manager.delegate = delegate;
return manager;
}
void FakeChromeIdentityService::IterateOverIdentities(
IdentityIteratorCallback callback) {
for (ChromeIdentity* identity in identities_) {
if (callback.Run(identity) == kIdentityIteratorInterruptIteration)
return;
}
}
void FakeChromeIdentityService::ForgetIdentity(
ChromeIdentity* identity,
ForgetIdentityCallback callback) {
[identities_ removeObject:identity];
[capabilitiesByIdentity_ removeObjectForKey:identity.gaiaID];
FireIdentityListChanged(/*keychain_reload=*/false);
if (callback) {
// Forgetting an identity is normally an asynchronous operation (that
// require some network calls), this is replicated here by dispatching
// it.
++_pendingCallback;
dispatch_async(dispatch_get_main_queue(), ^{
--_pendingCallback;
callback(nil);
});
}
}
void FakeChromeIdentityService::GetAccessToken(
ChromeIdentity* identity,
const std::string& client_id,
const std::set<std::string>& scopes,
ios::AccessTokenCallback callback) {
ios::AccessTokenCallback safe_callback = [callback copy];
NSError* error = nil;
NSDictionary* user_info = nil;
if (_fakeMDMError) {
// |GetAccessToken| is normally an asynchronous operation (that requires
// some network calls), this is replicated here by dispatching it.
error =
[NSError errorWithDomain:@"com.google.HTTPStatus" code:-1 userInfo:nil];
user_info = @{};
EXPECT_CALL(*this, HandleMDMNotification(identity, user_info, _))
.WillRepeatedly(testing::Return(true));
}
// |GetAccessToken| is normally an asynchronous operation (that requires some
// network calls), this is replicated here by dispatching it.
++_pendingCallback;
dispatch_async(dispatch_get_main_queue(), ^{
--_pendingCallback;
if (user_info)
FireAccessTokenRefreshFailed(identity, user_info);
// Token and expiration date. It should be larger than typical test
// execution because tests usually setup mock to expect one token request
// and then rely on access token being served from cache.
NSTimeInterval expiration = 60.0;
NSDate* expiresDate = [NSDate dateWithTimeIntervalSinceNow:expiration];
NSString* token = [expiresDate description];
safe_callback(token, expiresDate, error);
});
}
UIImage* FakeChromeIdentityService::GetCachedAvatarForIdentity(
ChromeIdentity* identity) {
return FakeGetCachedAvatarForIdentity(identity);
}
void FakeChromeIdentityService::GetAvatarForIdentity(
ChromeIdentity* identity,
GetAvatarCallback callback) {
if (!callback) {
return;
}
// |GetAvatarForIdentity| is normally an asynchronous operation, this is
// replicated here by dispatching it.
++_pendingCallback;
dispatch_async(dispatch_get_main_queue(), ^{
--_pendingCallback;
callback(FakeGetCachedAvatarForIdentity(identity));
});
}
void FakeChromeIdentityService::GetHostedDomainForIdentity(
ChromeIdentity* identity,
GetHostedDomainCallback callback) {
NSString* domain = FakeGetHostedDomainForIdentity(identity);
// |GetHostedDomainForIdentity| is normally an asynchronous operation , this
// is replicated here by dispatching it.
++_pendingCallback;
dispatch_async(dispatch_get_main_queue(), ^{
--_pendingCallback;
callback(domain, nil);
});
}
NSString* FakeChromeIdentityService::GetCachedHostedDomainForIdentity(
ChromeIdentity* identity) {
NSString* domain =
ChromeIdentityService::GetCachedHostedDomainForIdentity(identity);
if (domain) {
return domain;
}
return FakeGetHostedDomainForIdentity(identity);
}
void FakeChromeIdentityService::SimulateForgetIdentityFromOtherApp(
ChromeIdentity* identity) {
[identities_ removeObject:identity];
[capabilitiesByIdentity_ removeObjectForKey:identity.gaiaID];
FireChromeIdentityReload();
}
void FakeChromeIdentityService::FireChromeIdentityReload() {
FireIdentityListChanged(/*keychain_reload=*/true);
}
void FakeChromeIdentityService::SetUpForIntegrationTests() {}
void FakeChromeIdentityService::AddManagedIdentities(NSArray* identitiesNames) {
for (NSString* name in identitiesNames) {
NSString* email =
[NSString stringWithFormat:@"%@%@", name, kManagedIdentityEmailSuffix];
NSString* gaiaID = [NSString stringWithFormat:kIdentityGaiaIDFormat, name];
[identities_ addObject:[FakeChromeIdentity identityWithEmail:email
gaiaID:gaiaID
name:name]];
}
}
void FakeChromeIdentityService::AddIdentities(NSArray* identitiesNames) {
for (NSString* name in identitiesNames) {
NSString* email = [NSString stringWithFormat:kIdentityEmailFormat, name];
NSString* gaiaID = [NSString stringWithFormat:kIdentityGaiaIDFormat, name];
[identities_ addObject:[FakeChromeIdentity identityWithEmail:email
gaiaID:gaiaID
name:name]];
}
}
void FakeChromeIdentityService::AddIdentity(ChromeIdentity* identity) {
if (![identities_ containsObject:identity]) {
[identities_ addObject:identity];
}
FireIdentityListChanged(/*keychain_reload=*/false);
}
void FakeChromeIdentityService::SetCapabilities(ChromeIdentity* identity,
NSDictionary* capabilities) {
DCHECK([identities_ containsObject:identity]);
[capabilitiesByIdentity_ setObject:capabilities forKey:identity.gaiaID];
}
void FakeChromeIdentityService::SetFakeMDMError(bool fakeMDMError) {
_fakeMDMError = fakeMDMError;
}
bool FakeChromeIdentityService::WaitForServiceCallbacksToComplete() {
ConditionBlock condition = ^() {
return _pendingCallback == 0;
};
return WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, condition);
}
void FakeChromeIdentityService::TriggerIdentityUpdateNotification(
ChromeIdentity* identity) {
FireProfileDidUpdate(identity);
}
void FakeChromeIdentityService::FetchCapabilities(
NSArray* capabilities,
ChromeIdentity* identity,
ChromeIdentityCapabilitiesFetchCompletionBlock completion) {
NSMutableDictionary* result = [[NSMutableDictionary alloc] init];
NSDictionary* capabilitiesForIdentity =
capabilitiesByIdentity_[identity.gaiaID];
for (NSString* capability : capabilities) {
// Set capability result as unknown if not set in capabilitiesByIdentity_.
NSNumber* capabilityResult =
[NSNumber numberWithInt:static_cast<int>(
ChromeIdentityCapabilityResult::kUnknown)];
if ([capabilitiesForIdentity objectForKey:capability]) {
capabilityResult = capabilitiesForIdentity[capability];
}
[result setObject:capabilityResult forKey:capability];
}
if (completion) {
completion(result, nil);
}
}
} // namespace ios