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,
const std::string& device_name) {
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;
if (create_pairing)
......
......@@ -536,8 +536,12 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionPinProvided
object:self
userInfo:[NSDictionary dictionaryWithObject:pin
forKey:kHostSessionPin]];
userInfo:@{
kHostSessionHostName : _remoteHostName,
kHostSessionPin : pin,
kHostSessionCreatePairing :
[NSNumber numberWithBool:createPairing]
}];
}
- (void)didTapCancel:(id)sender {
......@@ -559,6 +563,8 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
state = ClientViewConnecting;
break;
case SessionPinPrompt:
_pinEntryView.supportsPairing = [[[notification userInfo]
objectForKey:kSessionSupportsPairing] boolValue];
state = ClientViewPinPrompt;
break;
case SessionConnected:
......
......@@ -26,6 +26,9 @@
// This delegate will handle interactions on the cells in the collection.
@property(weak, nonatomic) id<PinEntryDelegate> delegate;
// |supportsPairing| false will hide the remember pin checkbox.
@property(nonatomic) BOOL supportsPairing;
@end
#endif // REMOTING_IOS_APP_PIN_ENTRY_VIEW_H_
......@@ -28,6 +28,7 @@ static const int kMinPinLength = 6;
@implementation PinEntryView
@synthesize delegate = _delegate;
@synthesize supportsPairing = _supportsPairing;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
......@@ -82,6 +83,8 @@ static const int kMinPinLength = 6;
initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
_pairingSwitch, _pairingLabel,
_pinButton, _pinEntry)];
_supportsPairing = YES;
}
return self;
}
......@@ -133,6 +136,15 @@ static const int kMinPinLength = 6;
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
- (BOOL)textField:(UITextField*)textField
......
......@@ -93,7 +93,7 @@ RemotingAuthenticationStatus oauthStatusToRemotingAuthenticationStatus(
- (instancetype)init {
self = [super init];
if (self) {
_keychainWrapper = [[KeychainWrapper alloc] init];
_keychainWrapper = KeychainWrapper.instance;
_user = nil;
_firstLoadUserAttempt = YES;
}
......
......@@ -168,7 +168,21 @@ NSString* const kUserInfo = @"kUserInfo";
[_authentication
callbackWithAccessToken:^(RemotingAuthenticationStatus status,
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 @@
@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.
// TODO(nicholss): This will have to be futher refactored when we integrate
// with the private Google auth.
......@@ -18,9 +24,18 @@
- (void)setRefreshToken:(NSString*)refreshToken;
// Get the refresh token from the keychain, if there is one.
- (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.
- (void)resetKeychainItem;
// Access to the singleton shared instance from this property.
@property(nonatomic, readonly, class) KeychainWrapper* instance;
@end
#endif // REMOTING_IOS_KEYCHAIN_WRAPPER_H_
......@@ -8,10 +8,17 @@
#import "remoting/ios/keychain_wrapper.h"
#include "base/logging.h"
#import "remoting/ios/domain/host_info.h"
static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
NSString* const kPairingSecretSeperator = @"|";
NSString* const kKeychainPairingId = @"kKeychainPairingId";
NSString* const kKeychainPairingSecret = @"kKeychainPairingSecret";
@interface KeychainWrapper () {
NSMutableDictionary* _keychainData;
NSMutableDictionary* _userInfoQuery;
......@@ -20,6 +27,16 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
@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 {
if ((self = [super init])) {
OSStatus keychainErr = noErr;
......@@ -30,7 +47,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[NSData dataWithBytes:kKeychainItemIdentifier
length:strlen((const char*)kKeychainItemIdentifier)];
[_userInfoQuery setObject:keychainItemID
forKey:(__bridge id)kSecAttrGeneric];
forKey:(__bridge id)kSecAttrService];
[_userInfoQuery setObject:(__bridge id)kSecMatchLimitOne
forKey:(__bridge id)kSecMatchLimit];
[_userInfoQuery setObject:(__bridge id)kCFBooleanTrue
......@@ -61,6 +78,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
return self;
}
#pragma mark - Public
- (void)setRefreshToken:(NSString*)refreshToken {
[self setObject:refreshToken forKey:(__bridge id)kSecValueData];
}
......@@ -69,6 +88,74 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
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
// keychain:
- (void)setObject:(id)inObject forKey:(id)key {
......@@ -106,6 +193,8 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[_keychainData setObject:@"Gaia fresh token"
forKey:(__bridge id)kSecAttrDescription];
[_keychainData setObject:@"" forKey:(__bridge id)kSecValueData];
[_keychainData setObject:@"" forKey:(__bridge id)kSecClass];
[_keychainData setObject:@"" forKey:(__bridge id)kSecAttrGeneric];
}
- (NSMutableDictionary*)dictionaryToSecItemFormat:
......@@ -117,7 +206,7 @@ static const UInt8 kKeychainItemIdentifier[] = "org.chromium.RemoteDesktop\0";
[NSData dataWithBytes:kKeychainItemIdentifier
length:strlen((const char*)kKeychainItemIdentifier)];
[returnDictionary setObject:keychainItemID
forKey:(__bridge id)kSecAttrGeneric];
forKey:(__bridge id)kSecAttrService];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword
forKey:(__bridge id)kSecClass];
......
......@@ -30,7 +30,11 @@ extern NSString* const kHostSessionPinProvided;
// List of keys in user info from events.
extern NSString* const kSessionDetails;
extern NSString* const kSessionSupportsPairing;
extern NSString* const kSessonStateErrorCode;
extern NSString* const kHostSessionCreatePairing;
extern NSString* const kHostSessionHostName;
extern NSString* const kHostSessionPin;
// Remoting Client is the entry point for starting a session with a remote
......
......@@ -15,6 +15,7 @@
#import "remoting/ios/display/gl_display_handler.h"
#import "remoting/ios/domain/client_session_details.h"
#import "remoting/ios/domain/host_info.h"
#import "remoting/ios/keychain_wrapper.h"
#include "base/strings/sys_string_conversions.h"
#include "remoting/client/chromoting_client_runtime.h"
......@@ -31,7 +32,11 @@ NSString* const kHostSessionStatusChanged = @"kHostSessionStatusChanged";
NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided";
NSString* const kSessionDetails = @"kSessionDetails";
NSString* const kSessionSupportsPairing = @"kSessionSupportsPairing";
NSString* const kSessonStateErrorCode = @"kSessonStateErrorCode";
NSString* const kHostSessionCreatePairing = @"kHostSessionCreatePairing";
NSString* const kHostSessionHostName = @"kHostSessionHostName";
NSString* const kHostSessionPin = @"kHostSessionPin";
@interface RemotingClient () {
......@@ -91,10 +96,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs);
info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion);
info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion);
// TODO(nicholss): If iOS supports pairing, pull the stored data and
// insert it here.
info.pairing_id = "";
info.pairing_secret = "";
NSDictionary* pairing =
[KeychainWrapper.instance pairingCredentialsForHost:hostInfo.hostId];
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.
// info.capabilities =
......@@ -120,10 +133,11 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged
object:weakSelf
userInfo:[NSDictionary
dictionaryWithObject:strongSelf
->_sessionDetails
forKey:kSessionDetails]];
userInfo:@{
kSessionDetails : strongSelf->_sessionDetails,
kSessionSupportsPairing :
[NSNumber numberWithBool:pairing_supported],
}];
});
// TODO(nicholss): Add audio support to iOS.
......@@ -164,6 +178,18 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
- (void)hostSessionPinProvided:(NSNotification*)notification {
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) {
remoting::protocol::SecretFetchedCallback callback = _secretFetchedCallback;
_runtime->network_task_runner()->PostTask(
......@@ -267,7 +293,9 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
- (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)id
secret:(NSString*)secret {
NSLog(@"TODO(nicholss): implement this, commitPairingCredentialsForHost.");
[KeychainWrapper.instance commitPairingCredentialsForHost:host
id:id
secret:secret];
}
- (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