diff --git a/GirdersSwift.xcodeproj/project.pbxproj b/GirdersSwift.xcodeproj/project.pbxproj index affc119..3b557dd 100644 --- a/GirdersSwift.xcodeproj/project.pbxproj +++ b/GirdersSwift.xcodeproj/project.pbxproj @@ -40,7 +40,6 @@ CA7DAA051F866D140087BEA5 /* Configuration-env.plist in Resources */ = {isa = PBXBuildFile; fileRef = CA7DAA041F866D140087BEA5 /* Configuration-env.plist */; }; CAD5E2381F4C5DCD000B4DCE /* TestUtilURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD5E2371F4C5DCD000B4DCE /* TestUtilURL.swift */; }; CAD5E23A1F4C5DDA000B4DCE /* TestUtilString.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD5E2391F4C5DDA000B4DCE /* TestUtilString.swift */; }; - CAD5E23C1F4C5DEB000B4DCE /* TestUtilDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD5E23B1F4C5DEB000B4DCE /* TestUtilDictionary.swift */; }; CAEBA3201F4477A2003C9193 /* TestRequestGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAEBA31F1F4477A2003C9193 /* TestRequestGenerator.swift */; }; CAEBA3221F447CC1003C9193 /* TestHTTPUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAEBA3211F447CC1003C9193 /* TestHTTPUtil.swift */; }; E81F717220B5FEF5001B9B24 /* SecureStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E81F717120B5FEF5001B9B24 /* SecureStorage.swift */; }; @@ -129,7 +128,6 @@ CA7DAA041F866D140087BEA5 /* Configuration-env.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Configuration-env.plist"; sourceTree = ""; }; CAD5E2371F4C5DCD000B4DCE /* TestUtilURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtilURL.swift; sourceTree = ""; }; CAD5E2391F4C5DDA000B4DCE /* TestUtilString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtilString.swift; sourceTree = ""; }; - CAD5E23B1F4C5DEB000B4DCE /* TestUtilDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtilDictionary.swift; sourceTree = ""; }; CAEBA31F1F4477A2003C9193 /* TestRequestGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRequestGenerator.swift; sourceTree = ""; }; CAEBA3211F447CC1003C9193 /* TestHTTPUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestHTTPUtil.swift; sourceTree = ""; }; D3C42DCD874731A80E7D079E /* Pods-GirdersSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GirdersSwift.release.xcconfig"; path = "Pods/Target Support Files/Pods-GirdersSwift/Pods-GirdersSwift.release.xcconfig"; sourceTree = ""; }; @@ -286,7 +284,6 @@ CAEBA3211F447CC1003C9193 /* TestHTTPUtil.swift */, CAD5E2371F4C5DCD000B4DCE /* TestUtilURL.swift */, CAD5E2391F4C5DDA000B4DCE /* TestUtilString.swift */, - CAD5E23B1F4C5DEB000B4DCE /* TestUtilDictionary.swift */, CA74F82D1F4ECF5B00B1318F /* TestEndpoint.swift */, CA1C227E1F55565F00229381 /* TestRequest.swift */, CA3F78761F5EE674007F52EF /* TestHttpClient.swift */, @@ -732,7 +729,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - CAD5E23C1F4C5DEB000B4DCE /* TestUtilDictionary.swift in Sources */, CAD5E23A1F4C5DDA000B4DCE /* TestUtilString.swift in Sources */, CAD5E2381F4C5DCD000B4DCE /* TestUtilURL.swift in Sources */, CA19213D1F5567C6002615ED /* TestSerialization.swift in Sources */, diff --git a/GirdersSwift/src/main/swift/storage/KeychainStorage.swift b/GirdersSwift/src/main/swift/storage/KeychainStorage.swift index 4828904..2cc4fab 100644 --- a/GirdersSwift/src/main/swift/storage/KeychainStorage.swift +++ b/GirdersSwift/src/main/swift/storage/KeychainStorage.swift @@ -56,4 +56,53 @@ public class KeychainStorage: SecureStorage { } } + //MARK: - biometric protection + public func saveWithBiometricProtection(string: String?, + forKey key: String, + accessibility: Accessibility, + authenticationPolicy: AuthenticationPolicy) { + guard let string = string else { + do { + try keychain.remove(key) + } catch { + Log.error("Error removing keychain items") + } + return + } + DispatchQueue.global().async { [unowned self] in + do { + try self.keychain + .accessibility(accessibility, authenticationPolicy: authenticationPolicy) + .set(string, key: key) + } catch { + Log.error("Error saving value") + } + } + } + + public func saveWithBiometricProtection(string: String?, forKey key: String) { + self.saveWithBiometricProtection(string: string, + forKey: key, + accessibility: .whenPasscodeSetThisDeviceOnly, + authenticationPolicy: .userPresence) + } + + public func biometricProtectedString(forKey key: String, + withPrompt prompt: String, + result: @escaping (String?) -> Void) { + DispatchQueue.global().async { [unowned self] in + do { + let value = try self.keychain.authenticationPrompt(prompt).get(key) + DispatchQueue.main.async { + result(value) + } + } catch { + Log.error("Error retrieving the protected value") + DispatchQueue.main.async { + result(nil) + } + } + } + } + } diff --git a/GirdersSwift/src/main/swift/storage/SecureStorage.swift b/GirdersSwift/src/main/swift/storage/SecureStorage.swift index 86611a4..32e3d88 100644 --- a/GirdersSwift/src/main/swift/storage/SecureStorage.swift +++ b/GirdersSwift/src/main/swift/storage/SecureStorage.swift @@ -17,4 +17,10 @@ public protocol SecureStorage { func data(forKey key: String) -> Data? /// Clears the secure storage. func clearSecureStorage() + /// Saves a string with biometric protection. + func saveWithBiometricProtection(string: String?, forKey key: String) + /// Retrieves a string saved with biometric protection. + func biometricProtectedString(forKey key: String, + withPrompt prompt: String, + result: @escaping (String?) -> Void) } diff --git a/GirdersSwift/src/test/swift/http/TestUtilDictionary.swift b/GirdersSwift/src/test/swift/http/TestUtilDictionary.swift deleted file mode 100644 index 605d54c..0000000 --- a/GirdersSwift/src/test/swift/http/TestUtilDictionary.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// TestUtilDictionary.swift -// UnitTest -// -// Created by Angjel Kichukov on 22.08.17. -// Copyright © 2017 Netcetera AG. All rights reserved. -// - -import XCTest -@testable import GirdersSwift - -class TestUtilDictionary: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - //TODO: test different encoding / with umlauts, whitespaces, special characters - //TODO: test with nil - //Possible bug: Query is built from back to front - func testThatUrlEncodedQueryStringReturnsExpectedQueryString() { - var queryDictionary: [String:String] = [:] - - queryDictionary.updateValue("spam", forKey: "first") - queryDictionary.updateValue("ham", forKey: "second") - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - XCTAssertEqual(query, "second=ham&first=spam") - - } - - //Possible bug: Nil gets encoded as String - func testThatUrlEncodedQueryStringDoesNotEncodeNil() { - let queryDictionary = ["first" : "foo", "second" : nil ] - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - XCTAssertFalse(query.contains("nil")) - } - - func testThatUrlEncodedQueryStringEncodesWhiteSpaces() { - let queryDictionary = ["destination" : " Skopje ", "city" : "Zürich" ] - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - XCTAssertEqual(query, "city=Z%C3%BCrich&destination=%20%20Skopje%20%20%20%20") - } - - - func testThatUrlEncodedQueryStringEncodesSpecialCharacters() { - let queryDictionary = ["destination" : "Skopje#", "city" : "Zürich$" ] - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - XCTAssertEqual(query, "city=Z%C3%BCrich%24&destination=Skopje%23") - } - - func testThatUrlEncodedQueryStringEncodesUmlaumts() { - let queryDictionary = ["first" : "foo", "city" : "Zürich" ] - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - XCTAssertEqual(query, "city=Z%C3%BCrich&first=foo") - } - - func testThatUrlEncodedQueryStringReturnsSameEncodingForDifferentEncodings() { - let queryDictionary = ["destination" : "Skopje#", "city" : "Zürich$" ] - - let queryUtf8 = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - let queryAscii = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .ascii) - let queryIsoLatin1 = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .isoLatin1) - let queryUnicode = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .unicode) - - XCTAssertEqual(queryUtf8, "city=Z%C3%BCrich%24&destination=Skopje%23") - XCTAssertEqual(queryAscii, "city=Z%C3%BCrich%24&destination=Skopje%23") - XCTAssertEqual(queryIsoLatin1, "city=Z%C3%BCrich%24&destination=Skopje%23") - XCTAssertEqual(queryUnicode, "city=Z%C3%BCrich%24&destination=Skopje%23") - } - - func testThatUrlEncodedQueryStringEncodesBoolean() { - let queryDictionary = ["first" : true, "second" : false, "third" : false] - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - - XCTAssertEqual(query, "second=false&third=false&first=true") - } - - func testThatUrlEncodeQueryStringCanHandleEmptyDictionary() { - let queryDictionary = [String: Any]() - - let query = queryDictionary.urlEncodedQueryStringWithEncoding(encoding: .utf8) - - XCTAssertEqual(query, "") - - } - - func testThatUrlEncodeQueryStringWorksWithDifferentKeyTypes() { - let queryDict = [1 : "one", 2 : "two", 3 : "three"] - - let query = queryDict.urlEncodedQueryStringWithEncoding(encoding: .utf8) - - XCTAssertEqual(query, "2=two&3=three&1=one") - } -}