Permalink
Browse files

Add "keychain" route handler.

Uses SSKeychain to allow:

    - Querying the keychain for a list of all accounts, all accounts for
      a particular service, or the password for a particular
      service/account combination.
    - Modifying the keychain by deleting accounts.
    - Modifying the keychain by setting the password for a particular
      service/account combination.
  • Loading branch information...
jgallagher committed Feb 18, 2014
1 parent bf1c991 commit eac4a01d2175251afb1ec94d8aa781b661345809
@@ -26,6 +26,8 @@
52AB9FB818AFF46E00FA3C67 /* LPSSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 52AB9FB418AFF46E00FA3C67 /* LPSSKeychain.m */; };
52AB9FB918AFF46E00FA3C67 /* LPSSKeychainQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 52AB9FB518AFF46E00FA3C67 /* LPSSKeychainQuery.h */; };
52AB9FBA18AFF46E00FA3C67 /* LPSSKeychainQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 52AB9FB618AFF46E00FA3C67 /* LPSSKeychainQuery.m */; };
52AB9FBE18AFF4F500FA3C67 /* LPKeychainRoute.h in Headers */ = {isa = PBXBuildFile; fileRef = 52AB9FBC18AFF4F500FA3C67 /* LPKeychainRoute.h */; };
52AB9FBF18AFF4F500FA3C67 /* LPKeychainRoute.m in Sources */ = {isa = PBXBuildFile; fileRef = 52AB9FBD18AFF4F500FA3C67 /* LPKeychainRoute.m */; };
692D77A01657010000DE3E53 /* LPExitRoute.h in Headers */ = {isa = PBXBuildFile; fileRef = 692D779E1657010000DE3E53 /* LPExitRoute.h */; };
692D77A11657010000DE3E53 /* LPExitRoute.m in Sources */ = {isa = PBXBuildFile; fileRef = 692D779F1657010000DE3E53 /* LPExitRoute.m */; };
B12179A8165D613100B6DA43 /* LPScreenshotRoute2.h in Headers */ = {isa = PBXBuildFile; fileRef = B12179A6165D613100B6DA43 /* LPScreenshotRoute2.h */; };
@@ -150,6 +152,8 @@
52AB9FB518AFF46E00FA3C67 /* LPSSKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPSSKeychainQuery.h; sourceTree = "<group>"; };
52AB9FB618AFF46E00FA3C67 /* LPSSKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPSSKeychainQuery.m; sourceTree = "<group>"; };
52AB9FBB18AFF47C00FA3C67 /* SSKeychain.LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = SSKeychain.LICENSE; sourceTree = "<group>"; };
52AB9FBC18AFF4F500FA3C67 /* LPKeychainRoute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPKeychainRoute.h; sourceTree = "<group>"; };
52AB9FBD18AFF4F500FA3C67 /* LPKeychainRoute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPKeychainRoute.m; sourceTree = "<group>"; };
692D779E1657010000DE3E53 /* LPExitRoute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPExitRoute.h; sourceTree = "<group>"; };
692D779F1657010000DE3E53 /* LPExitRoute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPExitRoute.m; sourceTree = "<group>"; };
B111321514D5837900982BBC /* LPAsyncPlaybackRoute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPAsyncPlaybackRoute.h; sourceTree = "<group>"; };
@@ -582,6 +586,8 @@
B1CED0FA183E9551004793B9 /* LPLocationRoute.m */,
B1DDE6CB15C06CB500BBE2BE /* LPUserPrefRoute.h */,
B1DDE6CC15C06CB500BBE2BE /* LPUserPrefRoute.m */,
52AB9FBC18AFF4F500FA3C67 /* LPKeychainRoute.h */,
52AB9FBD18AFF4F500FA3C67 /* LPKeychainRoute.m */,
B16321F615BC200300408364 /* LPKeyboardRoute.h */,
B16321F715BC200300408364 /* LPKeyboardRoute.m */,
B16321F215BC1ECD00408364 /* LPGenericAsyncRoute.h */,
@@ -734,6 +740,7 @@
B1296FC015DC1F4B005DDF5C /* LPReflectUtils.h in Headers */,
B13B29251627389500433F6C /* LPISO8601DateFormatter.h in Headers */,
692D77A01657010000DE3E53 /* LPExitRoute.h in Headers */,
52AB9FBE18AFF4F500FA3C67 /* LPKeychainRoute.h in Headers */,
B12179A8165D613100B6DA43 /* LPScreenshotRoute2.h in Headers */,
B12DCD79189A833000F8DF87 /* LPLog.h in Headers */,
B1CED0FD183E9562004793B9 /* LPLocationRoute.h in Headers */,
@@ -922,6 +929,7 @@
B12DCD76189A82DF00F8DF87 /* LPEnv.m in Sources */,
B17E75AD15D4357F0066550B /* UIScriptASTWith.m in Sources */,
B17E75AE15D4357F0066550B /* UIScriptParser.m in Sources */,
52AB9FBF18AFF4F500FA3C67 /* LPKeychainRoute.m in Sources */,
B17E75AF15D4357F0066550B /* LPJSONUtils.m in Sources */,
B17E75B015D4357F0066550B /* LPTouchUtils.m in Sources */,
B17E75B115D4357F0066550B /* LPCDataScanner.m in Sources */,
@@ -13,6 +13,7 @@
#import "LPRecordRoute.h"
#import "LPAsyncPlaybackRoute.h"
#import "LPUserPrefRoute.h"
#import "LPKeychainRoute.h"
#import "LPAppPropertyRoute.h"
#import "LPQueryLogRoute.h"
#import "LPInterpolateRoute.h"
@@ -72,6 +73,10 @@ - (id) init {
[LPRouter addRoute:bgr forPath:@"userprefs"];
[bgr release];
LPKeychainRoute *keyr = [LPKeychainRoute new];
[LPRouter addRoute:keyr forPath:@"keychain"];
[keyr release];
LPAppPropertyRoute *appr = [LPAppPropertyRoute new];
[LPRouter addRoute:appr forPath:@"appproperty"];
[appr release];
@@ -0,0 +1,14 @@
//
// LPKeychainRoute.h
// calabash
//
// Created by John Gallagher on 2/15/14.
// Copyright (c) 2014 LessPainful. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "LPRoute.h"
@interface LPKeychainRoute : NSObject<LPRoute>
@end
@@ -0,0 +1,106 @@
//
// LPKeychainRoute.m
// calabash
//
// Created by John Gallagher on 2/15/14.
// Copyright (c) 2014 LessPainful. All rights reserved.
//
#import "LPKeychainRoute.h"
#import "LPJSONUtils.h"
#import "LPSSKeychain.h"
@implementation LPKeychainRoute
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path {
return [method isEqualToString:@"POST"] ||[method isEqualToString:@"GET"];
}
- (NSDictionary *)JSONResponseForMethod:(NSString *)method URI:(NSString *)path data:(NSDictionary*)data {
NSError *error;
if ([method isEqualToString:@"POST"])
{
NSString *service = data[@"service"];
if (!service) {
// no service - clear out the entire keychain
for (NSDictionary *d in [LPSSKeychain allAccounts]) {
NSString *service = d[kLPSSKeychainWhereKey];
NSString *account = d[kLPSSKeychainAccountKey];
if (![LPSSKeychain deletePasswordForService:service account:account error:&error]) {
return @{@"outcome": @"FAILURE",
@"reason": [NSString stringWithFormat:@"Error deleting password for %@ in service %@", account, service],
@"details": error.localizedDescription};
}
}
return @{@"outcome": @"SUCCESS", @"results": @[]};
}
NSString *account = data[@"account"];
if (!account) {
// no account - clear out all accounts for this service
for (NSDictionary *d in [LPSSKeychain accountsForService:service]) {
NSString *service = d[kLPSSKeychainWhereKey];
NSString *account = d[kLPSSKeychainAccountKey];
if (![LPSSKeychain deletePasswordForService:service account:account error:&error]) {
return @{@"outcome": @"FAILURE",
@"reason": [NSString stringWithFormat:@"Error deleting password for %@ in service %@", account, service],
@"details": error.localizedDescription};
}
}
return @{@"outcome": @"SUCCESS", @"results": @[]};
return @{@"outcome": @"FAILURE",
@"reason": @"Not yet implemented",
@"details": @""};
}
NSString *password = data[@"password"];
if (!password) {
// no password - delete this account's password
if ([LPSSKeychain deletePasswordForService:service account:account error:&error]) {
return @{@"outcome": @"SUCCESS", @"results": @[]};
} else {
return @{@"outcome": @"FAILURE",
@"reason": [NSString stringWithFormat:@"Error deleting password for %@ in service %@", account, service],
@"details": error.localizedDescription};
}
}
// Got service, account, and password - set it!
if ([LPSSKeychain setPassword:password forService:service account:account error:&error]) {
return @{@"outcome": @"SUCCESS", @"results": @[]};
} else {
return @{@"outcome": @"FAILURE",
@"reason": [NSString stringWithFormat:@"Error setting password for %@ in service %@", account, service],
@"details": error.localizedDescription};
}
}
NSString *service = data[@"service"];
if (!service) {
// not even a service - return all accounts
return @{@"outcome": @"SUCCESS",
@"results": [LPSSKeychain allAccounts] ?: @[]};
}
NSString *account = data[@"account"];
if (!account) {
// got a service but no account - return list of accounts
// for this service
return @{@"outcome": @"SUCCESS",
@"results": [LPSSKeychain accountsForService:service] ?: @[]};
}
// Got a service and an account; send back the password
NSString *password = [LPSSKeychain passwordForService:service
account:account
error:&error];
if (password) {
return @{@"outcome": @"SUCCESS", @"results": @[password]};
} else {
return @{@"outcome": @"FAILURE",

This comment has been minimized.

Show comment
Hide comment
@jmoody

jmoody Feb 24, 2014

@jgallagher

I am wondering if this should be a failure condition.

Why not return SUCCESS and an empty array?

Any thoughts?

@jmoody

jmoody Feb 24, 2014

@jgallagher

I am wondering if this should be a failure condition.

Why not return SUCCESS and an empty array?

Any thoughts?

This comment has been minimized.

Show comment
Hide comment
@jgallagher

jgallagher Feb 25, 2014

Owner

SSKeychain treats this as an error (and fills in the passed-in NSError). Unfortunately from its API, it's not obvious without inspecting the error whether this failed because there wasn't a record for the given service/account, or whether there was some other error reading from the keychain.

It wouldn't be too hard to create a "check for the existence of a given service/account record" from the Ruby side given the other entry points that already exist.

@jgallagher

jgallagher Feb 25, 2014

Owner

SSKeychain treats this as an error (and fills in the passed-in NSError). Unfortunately from its API, it's not obvious without inspecting the error whether this failed because there wasn't a record for the given service/account, or whether there was some other error reading from the keychain.

It wouldn't be too hard to create a "check for the existence of a given service/account record" from the Ruby side given the other entry points that already exist.

@"reason": [NSString stringWithFormat:@"Error looking up password for %@ in service %@", account, service],
@"details": error.localizedDescription};
}
}
@end

0 comments on commit eac4a01

Please sign in to comment.