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...
1 parent bf1c991 commit eac4a01d2175251afb1ec94d8aa781b661345809 @jgallagher committed Feb 18, 2014
@@ -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",
@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?

@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.