Commit f86db8ac authored by Scott Nichols's avatar Scott Nichols Committed by Commit Bot

This makes "remember my pin" checkbox work.

- Adding persistence via the keychain to remember secrets.
- Hide the checkbox based on host reported capabilities.

Change-Id: Ibf18726fd97636bd880395d44d6dc92a54ff1b69
Reviewed-on: https://chromium-review.googlesource.com/565660Reviewed-by: default avatarYuwei Huang <yuweih@chromium.org>
Commit-Queue: Scott Nichols <nicholss@chromium.org>
Cr-Commit-Position: refs/heads/master@{#485946}
parent 27e7cf78
...@@ -145,6 +145,12 @@ void ChromotingSession::ProvideSecret(const std::string& pin, ...@@ -145,6 +145,12 @@ void ChromotingSession::ProvideSecret(const std::string& pin,
const std::string& device_name) { const std::string& device_name) {
DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread()); DCHECK(runtime_->ui_task_runner()->BelongsToCurrentThread());
// TODO(nicholss): |pin| here is not used. Maybe there was an api refactor and
// this was not cleaned up. The auth pin providing mechanism seems to be call
// ProvideSecret, and then call the auth callback. When session moves to
// Connected state, this chromoing session calls RequestPairing based on
// create_pairing.
create_pairing_ = create_pairing; create_pairing_ = create_pairing;
if (create_pairing) if (create_pairing)
......
...@@ -536,8 +536,12 @@ static const CGFloat kKeyboardAnimationTime = 0.3; ...@@ -536,8 +536,12 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionPinProvided postNotificationName:kHostSessionPinProvided
object:self object:self
userInfo:[NSDictionary dictionaryWithObject:pin userInfo:@{
forKey:kHostSessionPin]]; kHostSessionHostName : _remoteHostName,
kHostSessionPin : pin,
kHostSessionCreatePairing :
[NSNumber numberWithBool:createPairing]
}];
} }
- (void)didTapCancel:(id)sender { - (void)didTapCancel:(id)sender {
...@@ -559,6 +563,8 @@ static const CGFloat kKeyboardAnimationTime = 0.3; ...@@ -559,6 +563,8 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
state = ClientViewConnecting; state = ClientViewConnecting;
break; break;
case SessionPinPrompt: case SessionPinPrompt:
_pinEntryView.supportsPairing = [[[notification userInfo]
objectForKey:kSessionSupportsPairing] boolValue];
state = ClientViewPinPrompt; state = ClientViewPinPrompt;
break; break;
case SessionConnected: case SessionConnected:
......
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
// This delegate will handle interactions on the cells in the collection. // This delegate will handle interactions on the cells in the collection.
@property(weak, nonatomic) id<PinEntryDelegate> delegate; @property(weak, nonatomic) id<PinEntryDelegate> delegate;
// |supportsPairing| false will hide the remember pin checkbox.
@property(nonatomic) BOOL supportsPairing;
@end @end
#endif // REMOTING_IOS_APP_PIN_ENTRY_VIEW_H_ #endif // REMOTING_IOS_APP_PIN_ENTRY_VIEW_H_
...@@ -28,6 +28,7 @@ static const int kMinPinLength = 6; ...@@ -28,6 +28,7 @@ static const int kMinPinLength = 6;
@implementation PinEntryView @implementation PinEntryView
@synthesize delegate = _delegate; @synthesize delegate = _delegate;
@synthesize supportsPairing = _supportsPairing;
- (id)initWithFrame:(CGRect)frame { - (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
...@@ -82,6 +83,8 @@ static const int kMinPinLength = 6; ...@@ -82,6 +83,8 @@ static const int kMinPinLength = 6;
initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings( initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
_pairingSwitch, _pairingLabel, _pairingSwitch, _pairingLabel,
_pinButton, _pinEntry)]; _pinButton, _pinEntry)];
_supportsPairing = YES;
} }
return self; return self;
} }
...@@ -133,6 +136,15 @@ static const int kMinPinLength = 6; ...@@ -133,6 +136,15 @@ static const int kMinPinLength = 6;
return [_pinEntry endEditing:force]; return [_pinEntry endEditing:force];
} }
#pragma mark - Properties
- (void)setSupportsPairing:(BOOL)supportsPairing {
_supportsPairing = supportsPairing;
_pairingSwitch.hidden = !_supportsPairing;
[_pairingSwitch setOn:NO animated:NO];
_pairingLabel.hidden = !_supportsPairing;
}
#pragma mark - UITextFieldDelegate #pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField*)textField - (BOOL)textField:(UITextField*)textField
......
...@@ -93,7 +93,7 @@ RemotingAuthenticationStatus oauthStatusToRemotingAuthenticationStatus( ...@@ -93,7 +93,7 @@ RemotingAuthenticationStatus oauthStatusToRemotingAuthenticationStatus(
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
if (self) { if (self) {
_keychainWrapper = [[KeychainWrapper alloc] init]; _keychainWrapper = KeychainWrapper.instance;
_user = nil; _user = nil;
_firstLoadUserAttempt = YES; _firstLoadUserAttempt = YES;
} }
......
...@@ -168,7 +168,21 @@ NSString* const kUserInfo = @"kUserInfo"; ...@@ -168,7 +168,21 @@ NSString* const kUserInfo = @"kUserInfo";
[_authentication [_authentication
callbackWithAccessToken:^(RemotingAuthenticationStatus status, callbackWithAccessToken:^(RemotingAuthenticationStatus status,
NSString* userEmail, NSString* accessToken) { NSString* userEmail, NSString* accessToken) {
[self startHostListFetchWith:accessToken]; switch (status) {
case RemotingAuthenticationStatusSuccess:
[self startHostListFetchWith:accessToken];
break;
case RemotingAuthenticationStatusNetworkError:
NSLog(
@"TODO(nicholss): implement this, "
@"RemotingAuthenticationStatusNetworkError.");
break;
case RemotingAuthenticationStatusAuthError:
NSLog(
@"TODO(nicholss): implement this, "
@"RemotingAuthenticationStatusAuthError.");
break;
}
}]; }];
} }
......
...@@ -9,6 +9,12 @@ ...@@ -9,6 +9,12 @@
@class UserInfo; @class UserInfo;
extern NSString* const kKeychainPairingId;
extern NSString* const kKeychainPairingSecret;
typedef void (^PairingCredentialsCallback)(NSString* pairingId,
NSString* secret);
// Class to abstract the details from how iOS wants to write to the keychain. // Class to abstract the details from how iOS wants to write to the keychain.
// TODO(nicholss): This will have to be futher refactored when we integrate // TODO(nicholss): This will have to be futher refactored when we integrate
// with the private Google auth. // with the private Google auth.
...@@ -18,9 +24,18 @@ ...@@ -18,9 +24,18 @@
- (void)setRefreshToken:(NSString*)refreshToken; - (void)setRefreshToken:(NSString*)refreshToken;
// Get the refresh token from the keychain, if there is one. // Get the refresh token from the keychain, if there is one.
- (NSString*)refreshToken; - (NSString*)refreshToken;
// Save the pairing credentials for the given host id.
- (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)pairingId
secret:(NSString*)secret;
// Get the pairing credentials for the given host id.
- (NSDictionary*)pairingCredentialsForHost:(NSString*)host;
// Reset the keychain and the cache. // Reset the keychain and the cache.
- (void)resetKeychainItem; - (void)resetKeychainItem;
// Access to the singleton shared instance from this property.
@property(nonatomic, readonly, class) KeychainWrapper* instance;
@end @end
#endif // REMOTING_IOS_KEYCHAIN_WRAPPER_H_ #endif // REMOTING_IOS_KEYCHAIN_WRAPPER_H_
...@@ -8,10 +8,17 @@ ...@@ -8,10 +8,17 @@
#import "remoting/ios/keychain_wrapper.h" #import "remoting/ios/keychain_wrapper.h"
#include "base/logging.h"
#import "remoting/ios/domain/host_info.h" #import "remoting/ios/domain/host_info.h"
static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
NSString* const kPairingSecretSeperator = @"|";
NSString* const kKeychainPairingId = @"kKeychainPairingId";
NSString* const kKeychainPairingSecret = @"kKeychainPairingSecret";
@interface KeychainWrapper () { @interface KeychainWrapper () {
NSMutableDictionary* _keychainData; NSMutableDictionary* _keychainData;
NSMutableDictionary* _userInfoQuery; NSMutableDictionary* _userInfoQuery;
...@@ -20,6 +27,16 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -20,6 +27,16 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
@implementation KeychainWrapper @implementation KeychainWrapper
// KeychainWrapper is a singleton.
+ (KeychainWrapper*)instance {
static KeychainWrapper* sharedInstance = nil;
static dispatch_once_t guard;
dispatch_once(&guard, ^{
sharedInstance = [[KeychainWrapper alloc] init];
});
return sharedInstance;
}
- (id)init { - (id)init {
if ((self = [super init])) { if ((self = [super init])) {
OSStatus keychainErr = noErr; OSStatus keychainErr = noErr;
...@@ -30,7 +47,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -30,7 +47,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[NSData dataWithBytes:kKeychainItemIdentifier [NSData dataWithBytes:kKeychainItemIdentifier
length:strlen((const char*)kKeychainItemIdentifier)]; length:strlen((const char*)kKeychainItemIdentifier)];
[_userInfoQuery setObject:keychainItemID [_userInfoQuery setObject:keychainItemID
forKey:(__bridge id)kSecAttrGeneric]; forKey:(__bridge id)kSecAttrService];
[_userInfoQuery setObject:(__bridge id)kSecMatchLimitOne [_userInfoQuery setObject:(__bridge id)kSecMatchLimitOne
forKey:(__bridge id)kSecMatchLimit]; forKey:(__bridge id)kSecMatchLimit];
[_userInfoQuery setObject:(__bridge id)kCFBooleanTrue [_userInfoQuery setObject:(__bridge id)kCFBooleanTrue
...@@ -61,6 +78,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -61,6 +78,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
return self; return self;
} }
#pragma mark - Public
- (void)setRefreshToken:(NSString*)refreshToken { - (void)setRefreshToken:(NSString*)refreshToken {
[self setObject:refreshToken forKey:(__bridge id)kSecValueData]; [self setObject:refreshToken forKey:(__bridge id)kSecValueData];
} }
...@@ -69,6 +88,74 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -69,6 +88,74 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
return [self objectForKey:(__bridge id)kSecValueData]; return [self objectForKey:(__bridge id)kSecValueData];
} }
- (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)pairingId
secret:(NSString*)secret {
NSString* keysString = [self objectForKey:(__bridge id)kSecAttrGeneric];
NSMutableDictionary* keys = [self stringToMap:keysString];
NSString* pairingIdAndSecret = [NSString
stringWithFormat:@"%@%@%@", pairingId, kPairingSecretSeperator, secret];
[keys setObject:pairingIdAndSecret forKey:host];
[self setObject:[self mapToString:keys] forKey:(__bridge id)kSecAttrGeneric];
}
- (NSDictionary*)pairingCredentialsForHost:(NSString*)host {
NSString* keysString = [self objectForKey:(__bridge id)kSecAttrGeneric];
NSMutableDictionary* keys = [self stringToMap:keysString];
NSString* pairingIdAndSecret = [keys objectForKey:host];
if (!pairingIdAndSecret ||
[pairingIdAndSecret rangeOfString:kPairingSecretSeperator].location ==
NSNotFound) {
return nil;
}
NSArray* components =
[pairingIdAndSecret componentsSeparatedByString:kPairingSecretSeperator];
DCHECK(components.count == 2);
return @{
kKeychainPairingId : components[0],
kKeychainPairingSecret : components[1],
};
}
#pragma mark - Map to String helpers
- (NSMutableDictionary*)stringToMap:(NSString*)mapString {
NSError* err;
if (mapString &&
[mapString respondsToSelector:@selector(dataUsingEncoding:)]) {
NSData* data = [mapString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* pairingMap;
if (data) {
pairingMap = (NSDictionary*)[NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&err];
}
if (!err) {
return [NSMutableDictionary dictionaryWithDictionary:pairingMap];
}
}
// failed to load a dictionary, make a new one.
return [NSMutableDictionary dictionaryWithCapacity:1];
}
- (NSString*)mapToString:(NSDictionary*)map {
if (map) {
NSError* err;
NSData* jsonData =
[NSJSONSerialization dataWithJSONObject:map options:0 error:&err];
if (!err) {
return [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
}
}
// failed to convert the map, make nil string.
return nil;
}
#pragma mark - Private
// Implement the mySetObject:forKey method, which writes attributes to the // Implement the mySetObject:forKey method, which writes attributes to the
// keychain: // keychain:
- (void)setObject:(id)inObject forKey:(id)key { - (void)setObject:(id)inObject forKey:(id)key {
...@@ -106,6 +193,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -106,6 +193,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[_keychainData setObject:@"Gaia fresh token" [_keychainData setObject:@"Gaia fresh token"
forKey:(__bridge id)kSecAttrDescription]; forKey:(__bridge id)kSecAttrDescription];
[_keychainData setObject:@"" forKey:(__bridge id)kSecValueData]; [_keychainData setObject:@"" forKey:(__bridge id)kSecValueData];
[_keychainData setObject:@"" forKey:(__bridge id)kSecClass];
[_keychainData setObject:@"" forKey:(__bridge id)kSecAttrGeneric];
} }
- (NSMutableDictionary*)dictionaryToSecItemFormat: - (NSMutableDictionary*)dictionaryToSecItemFormat:
...@@ -117,7 +206,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0"; ...@@ -117,7 +206,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[NSData dataWithBytes:kKeychainItemIdentifier [NSData dataWithBytes:kKeychainItemIdentifier
length:strlen((const char*)kKeychainItemIdentifier)]; length:strlen((const char*)kKeychainItemIdentifier)];
[returnDictionary setObject:keychainItemID [returnDictionary setObject:keychainItemID
forKey:(__bridge id)kSecAttrGeneric]; forKey:(__bridge id)kSecAttrService];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword [returnDictionary setObject:(__bridge id)kSecClassGenericPassword
forKey:(__bridge id)kSecClass]; forKey:(__bridge id)kSecClass];
......
...@@ -30,7 +30,11 @@ extern NSString* const kHostSessionPinProvided; ...@@ -30,7 +30,11 @@ extern NSString* const kHostSessionPinProvided;
// List of keys in user info from events. // List of keys in user info from events.
extern NSString* const kSessionDetails; extern NSString* const kSessionDetails;
extern NSString* const kSessionSupportsPairing;
extern NSString* const kSessonStateErrorCode; extern NSString* const kSessonStateErrorCode;
extern NSString* const kHostSessionCreatePairing;
extern NSString* const kHostSessionHostName;
extern NSString* const kHostSessionPin; extern NSString* const kHostSessionPin;
// Remoting Client is the entry point for starting a session with a remote // Remoting Client is the entry point for starting a session with a remote
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#import "remoting/ios/display/gl_display_handler.h" #import "remoting/ios/display/gl_display_handler.h"
#import "remoting/ios/domain/client_session_details.h" #import "remoting/ios/domain/client_session_details.h"
#import "remoting/ios/domain/host_info.h" #import "remoting/ios/domain/host_info.h"
#import "remoting/ios/keychain_wrapper.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "remoting/client/chromoting_client_runtime.h" #include "remoting/client/chromoting_client_runtime.h"
...@@ -31,7 +32,11 @@ NSString* const kHostSessionStatusChanged = @"kHostSessionStatusChanged"; ...@@ -31,7 +32,11 @@ NSString* const kHostSessionStatusChanged = @"kHostSessionStatusChanged";
NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided"; NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided";
NSString* const kSessionDetails = @"kSessionDetails"; NSString* const kSessionDetails = @"kSessionDetails";
NSString* const kSessionSupportsPairing = @"kSessionSupportsPairing";
NSString* const kSessonStateErrorCode = @"kSessonStateErrorCode"; NSString* const kSessonStateErrorCode = @"kSessonStateErrorCode";
NSString* const kHostSessionCreatePairing = @"kHostSessionCreatePairing";
NSString* const kHostSessionHostName = @"kHostSessionHostName";
NSString* const kHostSessionPin = @"kHostSessionPin"; NSString* const kHostSessionPin = @"kHostSessionPin";
@interface RemotingClient () { @interface RemotingClient () {
...@@ -91,10 +96,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin"; ...@@ -91,10 +96,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs); info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs);
info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion); info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion);
info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion); info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion);
// TODO(nicholss): If iOS supports pairing, pull the stored data and
// insert it here. NSDictionary* pairing =
info.pairing_id = ""; [KeychainWrapper.instance pairingCredentialsForHost:hostInfo.hostId];
info.pairing_secret = ""; if (pairing) {
info.pairing_id =
base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingId]);
info.pairing_secret =
base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingSecret]);
} else {
info.pairing_id = "";
info.pairing_secret = "";
}
// TODO(nicholss): I am not sure about the following fields yet. // TODO(nicholss): I am not sure about the following fields yet.
// info.capabilities = // info.capabilities =
...@@ -120,10 +133,11 @@ NSString* const kHostSessionPin = @"kHostSessionPin"; ...@@ -120,10 +133,11 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged postNotificationName:kHostSessionStatusChanged
object:weakSelf object:weakSelf
userInfo:[NSDictionary userInfo:@{
dictionaryWithObject:strongSelf kSessionDetails : strongSelf->_sessionDetails,
->_sessionDetails kSessionSupportsPairing :
forKey:kSessionDetails]]; [NSNumber numberWithBool:pairing_supported],
}];
}); });
// TODO(nicholss): Add audio support to iOS. // TODO(nicholss): Add audio support to iOS.
...@@ -164,6 +178,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin"; ...@@ -164,6 +178,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
- (void)hostSessionPinProvided:(NSNotification*)notification { - (void)hostSessionPinProvided:(NSNotification*)notification {
NSString* pin = [[notification userInfo] objectForKey:kHostSessionPin]; NSString* pin = [[notification userInfo] objectForKey:kHostSessionPin];
NSString* name = UIDevice.currentDevice.name;
BOOL createPairing = [[[notification userInfo]
objectForKey:kHostSessionCreatePairing] boolValue];
// TODO(nicholss): Look into refactoring ProvideSecret. It is mis-named and
// does not use pin.
if (_session) {
_session->ProvideSecret(base::SysNSStringToUTF8(pin),
(createPairing == YES),
base::SysNSStringToUTF8(name));
}
if (_secretFetchedCallback) { if (_secretFetchedCallback) {
remoting::protocol::SecretFetchedCallback callback = _secretFetchedCallback; remoting::protocol::SecretFetchedCallback callback = _secretFetchedCallback;
_runtime->network_task_runner()->PostTask( _runtime->network_task_runner()->PostTask(
...@@ -267,7 +293,9 @@ NSString* const kHostSessionPin = @"kHostSessionPin"; ...@@ -267,7 +293,9 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
- (void)commitPairingCredentialsForHost:(NSString*)host - (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)id id:(NSString*)id
secret:(NSString*)secret { secret:(NSString*)secret {
NSLog(@"TODO(nicholss): implement this, commitPairingCredentialsForHost."); [KeychainWrapper.instance commitPairingCredentialsForHost:host
id:id
secret:secret];
} }
- (void)fetchThirdPartyTokenForUrl:(NSString*)tokenUrl - (void)fetchThirdPartyTokenForUrl:(NSString*)tokenUrl
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment