Refactor accessible argument into options

This commit is contained in:
Joel Arvidsson 2017-02-10 10:58:39 +01:00
parent 1c4562a421
commit 6eb0a35037
4 changed files with 97 additions and 75 deletions

View File

@ -82,7 +82,9 @@ Keychain
Keychain Keychain
.getInternetCredentials(server) .getInternetCredentials(server)
.then(function(credentials) { .then(function(credentials) {
if (credentials) {
console.log('Credentials successfully loaded for user ' + credentials.username); console.log('Credentials successfully loaded for user ' + credentials.username);
}
}); });
Keychain Keychain

View File

@ -76,9 +76,9 @@ void rejectWithError(RCTPromiseRejectBlock reject, NSError *error)
return reject(codeForError(error), messageForError(error), nil); return reject(codeForError(error), messageForError(error), nil);
} }
CFStringRef accessibleValue(NSString *jsAccessibleKey) CFStringRef accessibleValue(NSDictionary *options)
{ {
if (jsAccessibleKey) { if (options && options[@"accessible"] != nil) {
NSDictionary *keyMap = @{ NSDictionary *keyMap = @{
@"AccessibleWhenUnlocked": (__bridge NSString *)kSecAttrAccessibleWhenUnlocked, @"AccessibleWhenUnlocked": (__bridge NSString *)kSecAttrAccessibleWhenUnlocked,
@"AccessibleAfterFirstUnlock": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock, @"AccessibleAfterFirstUnlock": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock,
@ -88,29 +88,35 @@ CFStringRef accessibleValue(NSString *jsAccessibleKey)
@"AccessibleAfterFirstUnlockThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, @"AccessibleAfterFirstUnlockThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
@"AccessibleAlwaysThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly @"AccessibleAlwaysThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly
}; };
NSString *result = [keyMap valueForKey:jsAccessibleKey]; NSString *result = keyMap[options[@"accessible"]];
if (result) { if (result) {
return (__bridge CFStringRef)result; return (__bridge CFStringRef)result;
} }
} }
return kSecAttrAccessibleAfterFirstUnlock; return kSecAttrAccessibleAfterFirstUnlock;
} }
RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(NSString*)username withPassword:(NSString*)password withAccessible:(NSString *)accessible resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ NSString *serviceValue(NSDictionary *options)
if(service == nil) { {
service = [[NSBundle mainBundle] bundleIdentifier]; if (options && options[@"service"] != nil) {
return options[@"service"];
} }
return [[NSBundle mainBundle] bundleIdentifier];
}
RCT_EXPORT_METHOD(setGenericPasswordForOptions:(NSDictionary *)options withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
NSString *service = serviceValue(options);
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, nil];
// Remove any old values from the keychain // Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict); OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add // Create dictionary of parameters to add
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, accessibleValue(accessible), kSecAttrAccessible, service, kSecAttrService, passwordData, kSecValueData, username, kSecAttrAccount, nil]; dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, accessibleValue(options), kSecAttrAccessible, service, kSecAttrService, passwordData, kSecValueData, username, kSecAttrAccount, nil];
// Try to save to keychain // Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL); osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
@ -124,13 +130,12 @@ RCT_EXPORT_METHOD(setGenericPasswordForService:(NSString*)service withUsername:(
} }
RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ RCT_EXPORT_METHOD(getGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
if(service == nil) { {
service = [[NSBundle mainBundle] bundleIdentifier]; NSString *service = serviceValue(options);
}
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Look up server in the keychain // Look up server in the keychain
NSDictionary* found = nil; NSDictionary* found = nil;
@ -148,7 +153,7 @@ RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service resolver:(RCTP
} }
// Found // Found
NSString* username = (NSString*) [found objectForKey:(__bridge id)(kSecAttrAccount)]; NSString* username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding]; NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
return resolve(@{ return resolve(@{
@ -159,13 +164,12 @@ RCT_EXPORT_METHOD(getGenericPasswordForService:(NSString*)service resolver:(RCTP
} }
RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ RCT_EXPORT_METHOD(resetGenericPasswordForOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
if(service == nil) { {
service = [[NSBundle mainBundle] bundleIdentifier]; NSString *service = serviceValue(options);
}
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassGenericPassword), kSecClass, service, kSecAttrService, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Remove any old values from the keychain // Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict); OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
@ -178,16 +182,17 @@ RCT_EXPORT_METHOD(resetGenericPasswordForService:(NSString*)service resolver:(RC
} }
RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password withAccessible:(NSString *)accessible resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString *)server withUsername:(NSString*)username withPassword:(NSString*)password withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, nil];
// Remove any old values from the keychain // Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict); OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
// Create dictionary of parameters to add // Create dictionary of parameters to add
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, accessibleValue(accessible), kSecAttrAccessible, server, kSecAttrServer, passwordData, kSecValueData, username, kSecAttrAccount, nil]; dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, accessibleValue(options), kSecAttrAccessible, server, kSecAttrServer, passwordData, kSecValueData, username, kSecAttrAccount, nil];
// Try to save to keychain // Try to save to keychain
osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL); osStatus = SecItemAdd((__bridge CFDictionaryRef) dict, NULL);
@ -200,13 +205,13 @@ RCT_EXPORT_METHOD(setInternetCredentialsForServer:(NSString*)server withUsername
return resolve(@(YES)); return resolve(@(YES));
} }
RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Look up server in the keychain // Look up server in the keychain
NSDictionary* found = nil; NSDictionary *found = nil;
CFTypeRef foundTypeRef = NULL; CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundTypeRef); OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) dict, (CFTypeRef*)&foundTypeRef);
@ -221,8 +226,8 @@ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server resolver:(RC
} }
// Found // Found
NSString* username = (NSString*) [found objectForKey:(__bridge id)(kSecAttrAccount)]; NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)];
NSString* password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding]; NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding];
return resolve(@{ return resolve(@{
@"server": server, @"server": server,
@ -232,10 +237,10 @@ RCT_EXPORT_METHOD(getInternetCredentialsForServer:(NSString*)server resolver:(RC
} }
RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString*)server resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ RCT_EXPORT_METHOD(resetInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
// Create dictionary of search parameters // Create dictionary of search parameters
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil]; NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)(kSecClassInternetPassword), kSecClass, server, kSecAttrServer, kCFBooleanTrue, kSecReturnAttributes, kCFBooleanTrue, kSecReturnData, nil];
// Remove any old values from the keychain // Remove any old values from the keychain
OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict); OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef) dict);
@ -274,7 +279,7 @@ RCT_EXPORT_METHOD(requestSharedWebCredentials:(RCTPromiseResolveBlock)resolve re
} }
RCT_EXPORT_METHOD(setSharedWebCredentialsForServer:(NSString*)server withUsername:(NSString*)username withPassword:(NSString*)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXPORT_METHOD(setSharedWebCredentialsForServer:(NSString *)server withUsername:(NSString *)username withPassword:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{ {
SecAddSharedWebCredential( SecAddSharedWebCredential(
(__bridge CFStringRef)server, (__bridge CFStringRef)server,

View File

@ -16,6 +16,7 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -43,7 +44,7 @@ public class KeychainModule extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void setGenericPasswordForService(String service, String username, String password, String accessible, Promise promise) { public void setGenericPasswordForOptions(String service, String username, String password, Promise promise) {
if (!crypto.isAvailable()) { if (!crypto.isAvailable()) {
Log.e(KEYCHAIN_MODULE, "Crypto is missing"); Log.e(KEYCHAIN_MODULE, "Crypto is missing");
promise.reject("KeychainModule: crypto is missing"); promise.reject("KeychainModule: crypto is missing");
@ -84,7 +85,7 @@ public class KeychainModule extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void getGenericPasswordForService(String service, Promise promise) { public void getGenericPasswordForOptions(String service, Promise promise) {
service = service == null ? EMPTY_STRING : service; service = service == null ? EMPTY_STRING : service;
String username = prefs.getString(service + ":u", "user_not_found"); String username = prefs.getString(service + ":u", "user_not_found");
@ -119,7 +120,7 @@ public class KeychainModule extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void resetGenericPasswordForService(String service, Promise promise) { public void resetGenericPasswordForOptions(String service, Promise promise) {
service = service == null ? EMPTY_STRING : service; service = service == null ? EMPTY_STRING : service;
SharedPreferences.Editor prefsEditor = prefs.edit(); SharedPreferences.Editor prefsEditor = prefs.edit();
@ -134,18 +135,18 @@ public class KeychainModule extends ReactContextBaseJavaModule {
} }
@ReactMethod @ReactMethod
public void setInternetCredentialsForServer(@NonNull String server, String username, String password, String accessible, Promise promise) { public void setInternetCredentialsForServer(@NonNull String server, String username, String password, ReadableMap unusedOptions, Promise promise) {
setGenericPasswordForService(server, username, password, accessible, promise); setGenericPasswordForOptions(server, username, password, promise);
} }
@ReactMethod @ReactMethod
public void getInternetCredentialsForServer(@NonNull String server, Promise promise) { public void getInternetCredentialsForServer(@NonNull String server, ReadableMap unusedOptions, Promise promise) {
getGenericPasswordForService(server, promise); getGenericPasswordForOptions(server, promise);
} }
@ReactMethod @ReactMethod
public void resetInternetCredentialsForServer(@NonNull String server, Promise promise) { public void resetInternetCredentialsForServer(@NonNull String server, ReadableMap unusedOptions, Promise promise) {
resetGenericPasswordForService(server, promise); resetGenericPasswordForOptions(server, promise);
} }

View File

@ -10,82 +10,96 @@ type SecAccessible =
| 'AccessibleAfterFirstUnlockThisDeviceOnly' | 'AccessibleAfterFirstUnlockThisDeviceOnly'
| 'AccessibleAlwaysThisDeviceOnly' | 'AccessibleAlwaysThisDeviceOnly'
type Options = {
accessible?: SecAccessible;
service?: string;
};
/** /**
* Saves the `username` and `password` combination for `server`. * Saves the `username` and `password` combination for `server`.
* @param {string} server URL to server. * @param {string} server URL to server.
* @param {string} username Associated username or e-mail to be saved. * @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved. * @param {string} password Associated password to be saved.
* @param {string} accessible (iOS only) kSecAccessibleKey * @param {object} options Keychain options, iOS only
* @return {Promise} Resolves to `true` when successful * @return {Promise} Resolves to `true` when successful
*/ */
export function setInternetCredentials( export function setInternetCredentials(
server: string, server: string,
username: string, username: string,
password: string, password: string,
accessible?: SecAccessible options?: Options
): Promise { ): Promise {
return RNKeychainManager.setInternetCredentialsForServer(server, username, password, accessible); return RNKeychainManager.setInternetCredentialsForServer(server, username, password, options);
} }
/** /**
* Fetches login combination for `server`. * Fetches login combination for `server`.
* @param {string} server URL to server. * @param {string} server URL to server.
* @param {object} options Keychain options, iOS only
* @return {Promise} Resolves to `{ server, username, password }` when successful * @return {Promise} Resolves to `{ server, username, password }` when successful
*/ */
export function getInternetCredentials( export function getInternetCredentials(
server: string server: string,
options?: Options
): Promise { ): Promise {
return RNKeychainManager.getInternetCredentialsForServer(server); return RNKeychainManager.getInternetCredentialsForServer(server, options);
} }
/** /**
* Deletes all internet password keychain entries for `server`. * Deletes all internet password keychain entries for `server`.
* @param {string} server URL to server. * @param {string} server URL to server.
* @param {object} options Keychain options, iOS only
* @return {Promise} Resolves to `true` when successful * @return {Promise} Resolves to `true` when successful
*/ */
export function resetInternetCredentials( export function resetInternetCredentials(
server: string server: string,
options?: Options
): Promise { ): Promise {
return RNKeychainManager.resetInternetCredentialsForServer(server); return RNKeychainManager.resetInternetCredentialsForServer(server, options);
}
function getOptionsArgument(serviceOrOptions?: string | KeychainOptions) {
if (Platform.OS !== 'ios') {
return typeof serviceOrOptions === 'object' ? serviceOrOptions.service : serviceOrOptions;
}
return typeof serviceOrOptions === 'string' ? { service: serviceOrOptions } : serviceOrOptions;
} }
/** /**
* Saves the `username` and `password` combination for `service`. * Saves the `username` and `password` combination for `service`.
* @param {string} username Associated username or e-mail to be saved. * @param {string} username Associated username or e-mail to be saved.
* @param {string} password Associated password to be saved. * @param {string} password Associated password to be saved.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`. * @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
* @param {string} accessible (iOS only) kSecAccessibleKey
* @return {Promise} Resolves to `true` when successful * @return {Promise} Resolves to `true` when successful
*/ */
export function setGenericPassword( export function setGenericPassword(
username: string, username: string,
password: string, password: string,
service?: string serviceOrOptions?: string | KeychainOptions
accessible?: SecAccessible
): Promise { ): Promise {
return RNKeychainManager.setGenericPasswordForService(service, username, password, accessible); return RNKeychainManager.setGenericPasswordForOptions(getOptionsArgument(serviceOrOptions), username, password);
} }
/** /**
* Fetches login combination for `service`. * Fetches login combination for `service`.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`. * @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
* @return {Promise} Resolves to `{ service, username, password }` when successful * @return {Promise} Resolves to `{ service, username, password }` when successful
*/ */
export function getGenericPassword( export function getGenericPassword(
service?: string serviceOrOptions?: string | KeychainOptions
): Promise { ): Promise {
return RNKeychainManager.getGenericPasswordForService(service); return RNKeychainManager.getGenericPasswordForOptions(getOptionsArgument(serviceOrOptions));
} }
/** /**
* Deletes all generic password keychain entries for `service`. * Deletes all generic password keychain entries for `service`.
* @param {string} service Reverse domain name qualifier for the service, defaults to `bundleId`. * @param {string|object} serviceOrOptions Reverse domain name qualifier for the service, defaults to `bundleId` or an options object.
* @return {Promise} Resolves to `true` when successful * @return {Promise} Resolves to `true` when successful
*/ */
export function resetGenericPassword( export function resetGenericPassword(
service?: string serviceOrOptions?: string | KeychainOptions
): Promise { ): Promise {
return RNKeychainManager.resetGenericPasswordForService(service); return RNKeychainManager.resetGenericPasswordForOptions(getOptionsArgument(serviceOrOptions));
} }
/** /**