Browse Source

Make methods non-class. Add keychain prefix.

Evgenii Neumerzhitckii 10 years ago
parent
commit
f92e44494c

+ 5 - 3
Demo/ViewController.swift

@@ -9,6 +9,8 @@ class ViewController: UIViewController {
   
   @IBOutlet weak var valueLabel: UILabel!
   
+  let keychain = KeychainSwift()
+  
   override func viewDidLoad() {
     super.viewDidLoad()
     
@@ -22,18 +24,18 @@ class ViewController: UIViewController {
   
   @IBAction func onSaveTapped(sender: AnyObject) {
     if let text = textField.text {
-      KeychainSwift.set(text, forKey: TegKeychainDemo_keyName)
+      keychain.set(text, forKey: TegKeychainDemo_keyName)
       updateValueLabel()
     }
   }
   
   @IBAction func onDeleteTapped(sender: AnyObject) {
-    KeychainSwift.delete(TegKeychainDemo_keyName)
+    keychain.delete(TegKeychainDemo_keyName)
     updateValueLabel()
   }
   
   private func updateValueLabel() {
-    if let value = KeychainSwift.get(TegKeychainDemo_keyName) {
+    if let value = keychain.get(TegKeychainDemo_keyName) {
       valueLabel.text = "In Keychain: \(value)"
     } else {
       valueLabel.text = "no value in keychain"

+ 4 - 0
KeychainSwift.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		2B7E7C4D1B81A3EC00D1C3B9 /* KeychainSwiftPrefixedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B7E7C4C1B81A3EC00D1C3B9 /* KeychainSwiftPrefixedTests.swift */; };
 		7ED6C9721B1C118F00FE8090 /* KeychainSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED6C9711B1C118F00FE8090 /* KeychainSwift.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		7ED6C9781B1C118F00FE8090 /* KeychainSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ED6C96C1B1C118F00FE8090 /* KeychainSwift.framework */; };
 		7ED6C9901B1C128100FE8090 /* KeychainSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED6C98D1B1C128100FE8090 /* KeychainSwift.swift */; };
@@ -57,6 +58,7 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
+		2B7E7C4C1B81A3EC00D1C3B9 /* KeychainSwiftPrefixedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainSwiftPrefixedTests.swift; sourceTree = "<group>"; };
 		7ED6C96C1B1C118F00FE8090 /* KeychainSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeychainSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7ED6C9701B1C118F00FE8090 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		7ED6C9711B1C118F00FE8090 /* KeychainSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeychainSwift.h; sourceTree = "<group>"; };
@@ -155,6 +157,7 @@
 			isa = PBXGroup;
 			children = (
 				7ED6C9941B1C12A200FE8090 /* KeychainSwiftTests.swift */,
+				2B7E7C4C1B81A3EC00D1C3B9 /* KeychainSwiftPrefixedTests.swift */,
 				7ED6C9931B1C12A200FE8090 /* KeychainSwiftTests-Bridging-Header.h */,
 				7ED6C97C1B1C118F00FE8090 /* Supporting Files */,
 			);
@@ -387,6 +390,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				2B7E7C4D1B81A3EC00D1C3B9 /* KeychainSwiftPrefixedTests.swift in Sources */,
 				7ED6C9971B1C12B300FE8090 /* KeychainSwiftAccessOptions.swift in Sources */,
 				7ED6C9961B1C12B100FE8090 /* KeychainSwift.swift in Sources */,
 				7ED6C9981B1C12B500FE8090 /* TegKeychainConstants.swift in Sources */,

+ 35 - 11
KeychainSwift/KeychainSwift.swift

@@ -9,7 +9,20 @@ A collection of helper functions for saving text and data in the keychain.
 */
 public class KeychainSwift {
   
-  static var lastQueryParameters: [String: NSObject]? // Used by the unit tests
+  var lastQueryParameters: [String: NSObject]? // Used by the unit tests
+
+  var keyPrefix = "" // Can be useful in test.
+  
+  public init() { }
+  
+  /**
+  
+  - parameter keyPrefix: a prefix that is added before the key in get/set methods. Note that `clear` method still clears everything from the Keychain.
+
+  */
+  public init(keyPrefix: String) {
+    self.keyPrefix = keyPrefix
+  }
   
   /**
   
@@ -20,7 +33,7 @@ public class KeychainSwift {
   - parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user.
 
   */
-  public class func set(value: String, forKey key: String,
+  public func set(value: String, forKey key: String,
     withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
     
     if let value = value.dataUsingEncoding(NSUTF8StringEncoding) {
@@ -41,14 +54,16 @@ public class KeychainSwift {
   - returns: True if the text was successfully written to the keychain.
   
   */
-  public class func set(value: NSData, forKey key: String,
+  public func set(value: NSData, forKey key: String,
     withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
 
     let accessible = access?.value ?? KeychainSwiftAccessOptions.defaultOption.value
       
+    let prefixedKey = keyWithPrefix(key)
+      
     let query = [
       KeychainSwiftConstants.klass       : KeychainSwiftConstants.classGenericPassword,
-      KeychainSwiftConstants.attrAccount : key,
+      KeychainSwiftConstants.attrAccount : prefixedKey,
       KeychainSwiftConstants.valueData   : value,
       KeychainSwiftConstants.accessible  : accessible
     ]
@@ -70,7 +85,7 @@ public class KeychainSwift {
   - returns: The text value from the keychain. Returns nil if unable to read the item.
   
   */
-  public class func get(key: String) -> String? {
+  public func get(key: String) -> String? {
     if let data = getData(key),
       let currentString = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
 
@@ -88,10 +103,12 @@ public class KeychainSwift {
   - returns: The text value from the keychain. Returns nil if unable to read the item.
   
   */
-  public class func getData(key: String) -> NSData? {
+  public func getData(key: String) -> NSData? {
+    let prefixedKey = keyWithPrefix(key)
+    
     let query = [
       KeychainSwiftConstants.klass       : kSecClassGenericPassword,
-      KeychainSwiftConstants.attrAccount : key,
+      KeychainSwiftConstants.attrAccount : prefixedKey,
       KeychainSwiftConstants.returnData  : kCFBooleanTrue,
       KeychainSwiftConstants.matchLimit  : kSecMatchLimitOne ]
     
@@ -114,10 +131,12 @@ public class KeychainSwift {
   - returns: True if the item was successfully deleted.
   
   */
-  public class func delete(key: String) -> Bool {
+  public func delete(key: String) -> Bool {
+    let prefixedKey = keyWithPrefix(key)
+
     let query = [
       KeychainSwiftConstants.klass       : kSecClassGenericPassword,
-      KeychainSwiftConstants.attrAccount : key ]
+      KeychainSwiftConstants.attrAccount : prefixedKey ]
     
     let status: OSStatus = SecItemDelete(query as CFDictionaryRef)
     
@@ -126,16 +145,21 @@ public class KeychainSwift {
 
   /**
   
-  Deletes all keychain items used by the app.
+  Deletes all Keychain items used by the app. Note that this method deletes all items regardless of the prefix settings used for initializing the class.
   
   - returns: True if the keychain items were successfully deleted.
   
   */
-  public class func clear() -> Bool {
+  public func clear() -> Bool {
     let query = [ kSecClass as String : kSecClassGenericPassword ]
     
     let status: OSStatus = SecItemDelete(query as CFDictionaryRef)
     
     return status == noErr
   }
+  
+  /// Returns the key with currently set prefix.
+  func keyWithPrefix(key: String) -> String {
+    return "\(keyPrefix)\(key)"
+  }
 }

+ 76 - 0
KeychainSwiftTests/KeychainSwiftPrefixedTests.swift

@@ -0,0 +1,76 @@
+import UIKit
+import XCTest
+
+class KeychainWithPrefixTests: XCTestCase {
+  
+  var prefixed: KeychainSwift!
+  var nonPrefixed: KeychainSwift!
+
+  
+  override func setUp() {
+    super.setUp()
+    
+    prefixed = KeychainSwift(keyPrefix: "test_prefix_")
+    nonPrefixed = KeychainSwift()
+    
+    prefixed.clear()
+    nonPrefixed.clear()
+    
+    prefixed.lastQueryParameters = nil
+  }
+  
+  func testKeyWithPrefix() {
+    XCTAssertEqual("test_prefix_key", prefixed.keyWithPrefix("key"))
+    XCTAssertEqual("key", nonPrefixed.keyWithPrefix("key"))
+  }
+  
+  // MARK: - Set text
+  // -----------------------
+  
+  func testSet() {
+    let key = "key 1"
+    XCTAssertTrue(prefixed.set("prefixed", forKey: key))
+    XCTAssertTrue(nonPrefixed.set("non prefixed", forKey: key))
+    
+    XCTAssertEqual("prefixed", prefixed.get(key)!)
+    XCTAssertEqual("non prefixed", nonPrefixed.get(key)!)
+  }
+  
+  
+  // MARK: - Set data
+  // -----------------------
+  
+  func testSetData() {
+    let key = "key 123"
+    
+    let dataPrefixed = "prefixed".dataUsingEncoding(NSUTF8StringEncoding)!
+    let dataNonPrefixed = "non prefixed".dataUsingEncoding(NSUTF8StringEncoding)!
+    
+    XCTAssertTrue(prefixed.set(dataPrefixed, forKey: key))
+    XCTAssertTrue(nonPrefixed.set(dataNonPrefixed, forKey: key))
+
+    
+    let dataFromKeychainPrefixed = prefixed.getData(key)!
+    let textFromKeychainPrefixed = NSString(data: dataFromKeychainPrefixed, encoding:NSUTF8StringEncoding) as! String
+    XCTAssertEqual("prefixed", textFromKeychainPrefixed)
+    
+    let dataFromKeychainNonPrefixed = nonPrefixed.getData(key)!
+    let textFromKeychainNonPrefixed = NSString(data: dataFromKeychainNonPrefixed, encoding:NSUTF8StringEncoding) as! String
+    XCTAssertEqual("non prefixed", textFromKeychainNonPrefixed)
+  }
+  
+  // MARK: - Delete
+  // -----------------------
+  
+  func testDelete() {
+    let key = "key 1"
+    XCTAssertTrue(prefixed.set("prefixed", forKey: key))
+    XCTAssertTrue(nonPrefixed.set("non prefixed", forKey: key))
+    
+    prefixed.delete(key)
+    
+    XCTAssert(prefixed.get(key) == nil)
+    XCTAssertFalse(nonPrefixed.get(key) == nil) // non-prefixed still exists
+  }
+  
+}

+ 29 - 25
KeychainSwiftTests/KeychainSwiftTests.swift

@@ -2,31 +2,35 @@ import UIKit
 import XCTest
 
 class keychainTests: XCTestCase {
+  
+  var obj: KeychainSwift!
+  
   override func setUp() {
     super.setUp()
     
-    KeychainSwift.clear()
-    KeychainSwift.lastQueryParameters = nil
+    obj = KeychainSwift()
+    obj.clear()
+    obj.lastQueryParameters = nil
   }
 
   // MARK: - Set text
   // -----------------------
 
   func testSet() {
-    XCTAssertTrue(KeychainSwift.set("hello :)", forKey: "key 1"))
-    XCTAssertEqual("hello :)", KeychainSwift.get("key 1")!)
+    XCTAssertTrue(obj.set("hello :)", forKey: "key 1"))
+    XCTAssertEqual("hello :)", obj.get("key 1")!)
   }
   
   func testSet_usesAccessibleWhenUnlockedByDefault() {
-    XCTAssertTrue(KeychainSwift.set("hello :)", forKey: "key 1"))
+    XCTAssertTrue(obj.set("hello :)", forKey: "key 1"))
     
-    let accessValue = KeychainSwift.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
+    let accessValue = obj.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
     XCTAssertEqual(KeychainSwiftAccessOptions.AccessibleWhenUnlocked.value, accessValue!)
   }
   
   func testSetWithAccessOption() {
-    KeychainSwift.set("hello :)", forKey: "key 1", withAccess: .AccessibleAfterFirstUnlock)
-    let accessValue = KeychainSwift.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
+    obj.set("hello :)", forKey: "key 1", withAccess: .AccessibleAfterFirstUnlock)
+    let accessValue = obj.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
     XCTAssertEqual(KeychainSwiftAccessOptions.AccessibleAfterFirstUnlock.value, accessValue!)
   }
   
@@ -36,9 +40,9 @@ class keychainTests: XCTestCase {
   func testSetData() {
     let data = "hello world".dataUsingEncoding(NSUTF8StringEncoding)!
     
-    XCTAssertTrue(KeychainSwift.set(data, forKey: "key 123"))
+    XCTAssertTrue(obj.set(data, forKey: "key 123"))
     
-    let dataFromKeychain = KeychainSwift.getData("key 123")!
+    let dataFromKeychain = obj.getData("key 123")!
     let textFromKeychain = NSString(data: dataFromKeychain, encoding:NSUTF8StringEncoding) as! String
     XCTAssertEqual("hello world", textFromKeychain)
   }
@@ -46,9 +50,9 @@ class keychainTests: XCTestCase {
   func testSetData_usesAccessibleWhenUnlockedByDefault() {
     let data = "hello world".dataUsingEncoding(NSUTF8StringEncoding)!
     
-    KeychainSwift.set(data, forKey: "key 123")
+    obj.set(data, forKey: "key 123")
     
-    let accessValue = KeychainSwift.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
+    let accessValue = obj.lastQueryParameters?[KeychainSwiftConstants.accessible] as? String
     XCTAssertEqual(KeychainSwiftAccessOptions.AccessibleWhenUnlocked.value, accessValue!)
   }
 
@@ -56,38 +60,38 @@ class keychainTests: XCTestCase {
   // -----------------------
 
   func testGet_returnNilWhenValueNotSet() {
-    XCTAssert(KeychainSwift.get("key 1") == nil)
+    XCTAssert(obj.get("key 1") == nil)
   }
 
   // MARK: - Delete
   // -----------------------
 
   func testDelete() {
-    KeychainSwift.set("hello :)", forKey: "key 1")
-    KeychainSwift.delete("key 1")
+    obj.set("hello :)", forKey: "key 1")
+    obj.delete("key 1")
     
-    XCTAssert(KeychainSwift.get("key 1") == nil)
+    XCTAssert(obj.get("key 1") == nil)
   }
 
   func testDelete_deleteOnSingleKey() {
-    KeychainSwift.set("hello :)", forKey: "key 1")
-    KeychainSwift.set("hello two", forKey: "key 2")
+    obj.set("hello :)", forKey: "key 1")
+    obj.set("hello two", forKey: "key 2")
 
-    KeychainSwift.delete("key 1")
+    obj.delete("key 1")
     
-    XCTAssertEqual("hello two", KeychainSwift.get("key 2")!)
+    XCTAssertEqual("hello two", obj.get("key 2")!)
   }
 
   // MARK: - Clear
   // -----------------------
 
   func testClear() {
-    KeychainSwift.set("hello :)", forKey: "key 1")
-    KeychainSwift.set("hello two", forKey: "key 2")
+    obj.set("hello :)", forKey: "key 1")
+    obj.set("hello two", forKey: "key 2")
     
-    KeychainSwift.clear()
+    obj.clear()
     
-    XCTAssert(KeychainSwift.get("key 1") == nil)
-    XCTAssert(KeychainSwift.get("key 2") == nil)
+    XCTAssert(obj.get("key 1") == nil)
+    XCTAssert(obj.get("key 2") == nil)
   }
 }

+ 21 - 7
README.md

@@ -43,21 +43,25 @@ Use the [previous version of the library](https://github.com/exchangegroup/keych
 Add `import KeychainSwift` to your source code if you used Carthage or CocoaPods setup methods.
 
 ```Swift
-KeychainSwift.set("hello world", forKey: "my key")
+let keychain = KeychainSwift()
 
-KeychainSwift.get("my key")
+keychain.set("hello world", forKey: "my key")
 
-KeychainSwift.delete("my key")
+keychain.get("my key")
 
-KeychainSwift.clear() // delete everything from app's Keychain
+keychain.delete("my key")
+
+keychain.clear() // delete everything from app's Keychain
 ```
 
 In addition to strings one can set/get `NSData` objects.
 
 ```Swift
-KeychainSwift.set(nsDataObject, forKey: "my key")
+let keychain = KeychainSwift()
+
+keychain.set(nsDataObject, forKey: "my key")
 
-KeychainSwift.getData("my key")
+keychain.getData("my key")
 ```
 
 ## Advanced options
@@ -68,13 +72,23 @@ Use `withAccess` parameter to specify the security level of the keychain storage
 By default the `.AccessibleWhenUnlocked` option is used. It is one of the most restrictive options and provides good data protection.
 
 ```
-KeychainSwift.set("Hello world", forKey: "key 1", withAccess: .AccessibleWhenUnlocked)
+KeychainSwift().set("Hello world", forKey: "key 1", withAccess: .AccessibleWhenUnlocked)
 ```
 
 You can use `.AccessibleAfterFirstUnlock` if you need your app to access the keychain item while in the background.  It may be needed for the Apple Watch apps. Note that it is less secure than the `.AccessibleWhenUnlocked` option.
 
 See the list of all available [access options](https://github.com/exchangegroup/keychain-swift/blob/master/KeychainSwift/KeychainSwiftAccessOptions.swift).
 
+### Setting key prefix
+
+One can pass a `keyPrefix` argument when initializing a `KeychainSwift` object. The string passed in `keyPrefix` argument will be used as a prefix to the keys supplied in set, get, getData and delete methods. I use the prefixed keychain in test. This prevents the tests from changing the Keychain keys that are used when the app is launched manually.
+
+Note that `clear` method still clears everything from the Keychain.
+
+```Swift
+let keychain = KeychainSwift(keyPrefix: "myTestKey_")
+```
+
 ## Demo app
 
 <img src="https://raw.githubusercontent.com/exchangegroup/keychain-swift/master/graphics/keychain-swift-demo.png" alt="Sacing and reading text from Keychaing in iOS and Swift" width="320">