From 144b08fda87b9534b783f814e96089fd622f9830 Mon Sep 17 00:00:00 2001 From: abe Date: Wed, 7 Jul 2021 01:52:41 -0500 Subject: [PATCH 1/4] =?UTF-8?q?Se=20agrega=20un=20servicio=20responsable?= =?UTF-8?q?=20del=20manejo=20del=20Keychain=20y=20las=20pruebas=20correspo?= =?UTF-8?q?ndientes=20a=20este.=20Falta=20implementar=20la=20accesibilidad?= =?UTF-8?q?=20de=20los=20datos=20en=20el=20servicio=20e=20integrar=20con?= =?UTF-8?q?=20el=20c=C3=B3digo=20correspondiente=20de=20la=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Secretly/Services/KeychainService.swift | 129 ++++++++++++++++++++++++ SecretlyTests/KeychainServiceTest.swift | 85 ++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 Secretly/Services/KeychainService.swift create mode 100644 SecretlyTests/KeychainServiceTest.swift diff --git a/Secretly/Services/KeychainService.swift b/Secretly/Services/KeychainService.swift new file mode 100644 index 0000000..db917bc --- /dev/null +++ b/Secretly/Services/KeychainService.swift @@ -0,0 +1,129 @@ +// +// KeychainService.swift +// Secretly +// +// Created by Luis Abraham Ortega Gonzalez on 02/07/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + + + + +enum KeychainError: Error { + case noItem + case unexpectedItemData + case unhandledError(status: OSStatus) +} + + +struct KeychainService{ + + public let serviceName:String? + + private let defaultServiceName: String = { + return Bundle.main.bundleIdentifier ?? "com.secretly.ioslab" + }() + + public init(serviceName:String? = nil){ + self.serviceName = serviceName + } + + + public func setItem(key :String, value: String) -> Bool { + let encodedValue = value.data(using: String.Encoding.utf8)! + return self.set(key: key, value: encodedValue) + } + + + private func set(key :String, value: Data) -> Bool{ + var query = self.setUpKeychainDic(key: key) + + query[kSecValueData as String] = value + + let status = SecItemAdd(query as CFDictionary, nil) + if status == errSecSuccess { + return true + } else if status == errSecDuplicateItem { + return self.updateItem(key: key, value: value) + } else { + return false + } + } + + private func updateItem(key:String, value:Data) -> Bool { + + let query = self.setUpKeychainDic(key: key) + let update:[String:Any] = [kSecValueData as String:value] + + let status = SecItemUpdate(query as CFDictionary, update as CFDictionary) + + if status == errSecSuccess { + return true + } else { + return false + } + } + + + public func getItem(forKey key: String) throws -> String{ + var query = self.setUpKeychainDic(key: key) + query[kSecReturnAttributes as String] = true + query[kSecReturnData as String] = true + query[kSecMatchLimit as String] = kSecMatchLimitOne + + var item: CFTypeRef? + let status = SecItemCopyMatching(query as CFDictionary, &item) + + + guard status != errSecItemNotFound else { throw KeychainError.noItem } + guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) } + + guard let existingItem = item as? [String : Any], + let itemData = existingItem[kSecValueData as String] as? Data, + let stringData = String(data: itemData, encoding: String.Encoding.utf8) + else { + throw KeychainError.unexpectedItemData + } + + return stringData + } + + + public func deleteItem(forKey key: String) -> Bool{ + var query = self.setUpKeychainDic(key: key) + let status: OSStatus = SecItemDelete(query as CFDictionary) + if status == errSecSuccess { + return true + } else { + return false + } + + } + + public func deleteAllItems() -> Bool { + var query = [String:Any]() + query[kSecClass as String] = kSecClassInternetPassword + query[kSecAttrServer as String] = self.serviceName ?? self.defaultServiceName + + let status: OSStatus = SecItemDelete(query as CFDictionary) + if status == errSecSuccess { + return true + } else { + return false + } + } + + private func setUpKeychainDic(key: String) -> [String:Any]{ + var keychainQueryDictionary: [String:Any] = [String: Any]() + // Uniquely identify this keychain accessor + let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8) + keychainQueryDictionary[kSecClass as String] = kSecClassInternetPassword + keychainQueryDictionary[kSecAttrDescription as String] = encodedIdentifier + keychainQueryDictionary[kSecAttrAccount as String] = encodedIdentifier + keychainQueryDictionary[kSecAttrServer as String] = self.serviceName ?? self.defaultServiceName + return keychainQueryDictionary + } + +} diff --git a/SecretlyTests/KeychainServiceTest.swift b/SecretlyTests/KeychainServiceTest.swift new file mode 100644 index 0000000..d91584b --- /dev/null +++ b/SecretlyTests/KeychainServiceTest.swift @@ -0,0 +1,85 @@ +// +// Keychain.swift +// SecretlyTests +// +// Created by Luis Abraham Ortega Gonzalez on 03/07/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import XCTest + +@testable import Secretly + +class KeychainServiceTest: XCTestCase { + + var keychain : KeychainService? + let testString = "testString" + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + self.keychain = KeychainService(serviceName:"test.secretly") + let encodedIdentifier = self.testString.data(using: .utf8) + let encodedValue = self.testString.data(using: .utf8) + var query:[String:Any] = [:] + query[kSecClass as String] = kSecClassInternetPassword + query[kSecAttrServer as String] = "test.secretly" + query[kSecAttrDescription as String] = encodedIdentifier + query[kSecAttrAccount as String] = encodedIdentifier + query[kSecValueData as String] = encodedValue + + let status = SecItemAdd(query as CFDictionary, nil) + + + + + } + + override func tearDownWithError() throws { + var query:[String:Any] = [:] + query[kSecClass as String] = kSecClassInternetPassword + query[kSecAttrServer as String] = "test.secretly" + + let status: OSStatus = SecItemDelete(query as CFDictionary) + self.keychain = nil + + } + + + func testSaveValueToKeychain(){ + let result = self.keychain?.setItem(key:"string1", value:"string1") + + XCTAssertTrue(result ?? false) + } + + + func testReadValueFromKeyChain(){ + do { + let result = try self.keychain?.getItem(forKey: self.testString) + XCTAssertEqual(result, self.testString) + } catch { + print("Ocurrió un error durante la lectura \(error.localizedDescription) ") + } + } + + func testDeleteAnItemFromKeyChain(){ + let result = self.keychain?.deleteItem(forKey:self.testString) + XCTAssertTrue(result ?? false) + var thrownError:Error? = nil + XCTAssertThrowsError(try self.keychain?.getItem(forKey: self.testString)) { + thrownError = $0 + } + } + + + func testDeleteAllValuesFromKeyChain(){ + let result = self.keychain?.deleteAllItems() + XCTAssertTrue(result ?? false) + var thrownError:Error? = nil + XCTAssertThrowsError(try self.keychain?.getItem(forKey: self.testString)) { + thrownError = $0 + } + } + + + +} From 4cb55015ca215df20c92b5e49dc8a477363ead74 Mon Sep 17 00:00:00 2001 From: abe Date: Thu, 8 Jul 2021 01:27:24 -0500 Subject: [PATCH 2/4] =?UTF-8?q?Se=20agrega=20la=20funcionalidad=20para=20a?= =?UTF-8?q?=C3=B1adir=20la=20accesibilidad=20a=20cada=20uno=20de=20los=20i?= =?UTF-8?q?tems=20a=20a=C3=B1adir=20al=20keychain.=20Ahora=20es=20posible?= =?UTF-8?q?=20restringir=20el=20acceso=20seg=C3=BAn=20se=20indique=20al=20?= =?UTF-8?q?a=C3=B1adirlos.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Secretly/Services/KeychainService.swift | 29 ++++++--- SecretlyTests/KeychainServiceTest.swift | 81 ++++++++++++++++--------- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/Secretly/Services/KeychainService.swift b/Secretly/Services/KeychainService.swift index db917bc..4ab0e5e 100644 --- a/Secretly/Services/KeychainService.swift +++ b/Secretly/Services/KeychainService.swift @@ -18,6 +18,9 @@ enum KeychainError: Error { } + + + struct KeychainService{ public let serviceName:String? @@ -31,14 +34,14 @@ struct KeychainService{ } - public func setItem(key :String, value: String) -> Bool { + public func setItem(key :String, value: String, accesibility: CFString? = nil) -> Bool { let encodedValue = value.data(using: String.Encoding.utf8)! - return self.set(key: key, value: encodedValue) + return self.set(key: key, value: encodedValue, accesibility: accesibility) } - private func set(key :String, value: Data) -> Bool{ - var query = self.setUpKeychainDic(key: key) + private func set(key :String, value: Data, accesibility: CFString? = nil) -> Bool{ + var query = self.setUpKeychainDic(key: key,accesibility: accesibility) query[kSecValueData as String] = value @@ -46,15 +49,15 @@ struct KeychainService{ if status == errSecSuccess { return true } else if status == errSecDuplicateItem { - return self.updateItem(key: key, value: value) + return self.updateItem(key: key, value: value, accesibility: accesibility) } else { return false } } - private func updateItem(key:String, value:Data) -> Bool { + private func updateItem(key:String, value:Data, accesibility: CFString? = nil) -> Bool { - let query = self.setUpKeychainDic(key: key) + let query = self.setUpKeychainDic(key: key, accesibility: accesibility) let update:[String:Any] = [kSecValueData as String:value] let status = SecItemUpdate(query as CFDictionary, update as CFDictionary) @@ -91,8 +94,8 @@ struct KeychainService{ } - public func deleteItem(forKey key: String) -> Bool{ - var query = self.setUpKeychainDic(key: key) + public func deleteItem(forKey key: String, accesibility: CFString? = nil) -> Bool{ + let query = self.setUpKeychainDic(key: key, accesibility: accesibility) let status: OSStatus = SecItemDelete(query as CFDictionary) if status == errSecSuccess { return true @@ -115,7 +118,7 @@ struct KeychainService{ } } - private func setUpKeychainDic(key: String) -> [String:Any]{ + private func setUpKeychainDic(key: String, accesibility: CFString? = nil) -> [String:Any]{ var keychainQueryDictionary: [String:Any] = [String: Any]() // Uniquely identify this keychain accessor let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8) @@ -123,6 +126,12 @@ struct KeychainService{ keychainQueryDictionary[kSecAttrDescription as String] = encodedIdentifier keychainQueryDictionary[kSecAttrAccount as String] = encodedIdentifier keychainQueryDictionary[kSecAttrServer as String] = self.serviceName ?? self.defaultServiceName + + if let accessString = accesibility { + keychainQueryDictionary[kSecAttrAccessible as String] = accessString + } + + return keychainQueryDictionary } diff --git a/SecretlyTests/KeychainServiceTest.swift b/SecretlyTests/KeychainServiceTest.swift index d91584b..4d5f6c5 100644 --- a/SecretlyTests/KeychainServiceTest.swift +++ b/SecretlyTests/KeychainServiceTest.swift @@ -10,14 +10,13 @@ import XCTest @testable import Secretly + class KeychainServiceTest: XCTestCase { - - var keychain : KeychainService? + let serviceName = "test.secretly" let testString = "testString" override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. - self.keychain = KeychainService(serviceName:"test.secretly") let encodedIdentifier = self.testString.data(using: .utf8) let encodedValue = self.testString.data(using: .utf8) var query:[String:Any] = [:] @@ -27,11 +26,7 @@ class KeychainServiceTest: XCTestCase { query[kSecAttrAccount as String] = encodedIdentifier query[kSecValueData as String] = encodedValue - let status = SecItemAdd(query as CFDictionary, nil) - - - - + _ = SecItemAdd(query as CFDictionary, nil) } override func tearDownWithError() throws { @@ -40,46 +35,76 @@ class KeychainServiceTest: XCTestCase { query[kSecAttrServer as String] = "test.secretly" let status: OSStatus = SecItemDelete(query as CFDictionary) - self.keychain = nil } func testSaveValueToKeychain(){ - let result = self.keychain?.setItem(key:"string1", value:"string1") + let test = "string1" + let keychain = KeychainService(serviceName:self.serviceName) + + let result = keychain.setItem(key:test, value:test) - XCTAssertTrue(result ?? false) + XCTAssertTrue(result) } func testReadValueFromKeyChain(){ - do { - let result = try self.keychain?.getItem(forKey: self.testString) - XCTAssertEqual(result, self.testString) - } catch { - print("Ocurrió un error durante la lectura \(error.localizedDescription) ") + let keychain = KeychainService(serviceName:self.serviceName) + + + let result = try? keychain.getItem(forKey: self.testString) + XCTAssertEqual(result, self.testString) + + } + + func testUpdateValueInKeychain(){ + let value = "test2" + let keychain = KeychainService(serviceName:self.serviceName) + + let updated = keychain.setItem(key: self.testString, value: value) + + XCTAssertTrue(updated) + let result = try? keychain.getItem(forKey: self.testString) + XCTAssertEqual(result, value) + + } + + + func testReadNoValueFromKeyChain(){ + let keychain = KeychainService(serviceName:self.serviceName) + let key = "test2" + + XCTAssertThrowsError(try keychain.getItem(forKey: key)) { + switch $0{ + case KeychainError.noItem: + XCTAssertTrue(true) + default: + XCTAssertTrue(false) + } } } + func testDeleteAnItemFromKeyChain(){ - let result = self.keychain?.deleteItem(forKey:self.testString) - XCTAssertTrue(result ?? false) - var thrownError:Error? = nil - XCTAssertThrowsError(try self.keychain?.getItem(forKey: self.testString)) { - thrownError = $0 - } + let keychain = KeychainService(serviceName:self.serviceName) + + let result = keychain.deleteItem(forKey:self.testString) + + XCTAssertTrue(result) + XCTAssertThrowsError(try keychain.getItem(forKey: self.testString)) } func testDeleteAllValuesFromKeyChain(){ - let result = self.keychain?.deleteAllItems() - XCTAssertTrue(result ?? false) - var thrownError:Error? = nil - XCTAssertThrowsError(try self.keychain?.getItem(forKey: self.testString)) { - thrownError = $0 - } + let keychain = KeychainService(serviceName:self.serviceName) + + let result = keychain.deleteAllItems() + XCTAssertTrue(result) + XCTAssertThrowsError(try keychain.getItem(forKey: self.testString)) } } + From 8a4deff3ae74c913265511b3c22ce5e02ebbb929 Mon Sep 17 00:00:00 2001 From: abe Date: Mon, 12 Jul 2021 14:27:52 -0500 Subject: [PATCH 3/4] =?UTF-8?q?Se=20cambia=20el=20KeychainService=20para?= =?UTF-8?q?=20que=20quede=20alojado=20en=20la=20carpeta=20de=20storage,=20?= =?UTF-8?q?La=20clase=20Keychain=20storage=20cuenta=20con=20una=20instanci?= =?UTF-8?q?a=20estilo=20Singleton=20que=20no=20prohibe=20la=20creaci=C3=B3?= =?UTF-8?q?n=20de=20otras=20instancias,=20esto=20para=20poder=20cambiar=20?= =?UTF-8?q?el=20nombre=20del=20servicio=20para=20el=20Keychain=20de=20ser?= =?UTF-8?q?=20necesario.=20Se=20mueven=20los=20errores=20a=20un=20archivo?= =?UTF-8?q?=20llamado=20KeychainError=20es=20un=20enumerable.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Secretly.xcodeproj/project.pbxproj | 12 +++++++++ Secretly/Models/CurrentUser.swift | 8 +++--- Secretly/Network/AmacaConfig.swift | 5 ++-- Secretly/Storage/KeychainError.swift | 15 +++++++++++ .../KeychainStore.swift} | 27 ++++++++----------- ...t.swift => KeychainStoreServiceTest.swift} | 14 +++++----- 6 files changed, 52 insertions(+), 29 deletions(-) create mode 100644 Secretly/Storage/KeychainError.swift rename Secretly/{Services/KeychainService.swift => Storage/KeychainStore.swift} (91%) rename SecretlyTests/{KeychainServiceTest.swift => KeychainStoreServiceTest.swift} (85%) diff --git a/Secretly.xcodeproj/project.pbxproj b/Secretly.xcodeproj/project.pbxproj index 9084066..664f44f 100644 --- a/Secretly.xcodeproj/project.pbxproj +++ b/Secretly.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 3013628D269A91E50001580D /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3013628C269A91E50001580D /* KeychainError.swift */; }; + 301649F7268F967E00F26F4D /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 301649F6268F967E00F26F4D /* KeychainStore.swift */; }; 302B5845267E658E007133E6 /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583D267E658E007133E6 /* HttpResponse.swift */; }; 302B5846267E658E007133E6 /* AmacaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583E267E658E007133E6 /* AmacaConfig.swift */; }; 302B5847267E658E007133E6 /* StatusCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302B583F267E658E007133E6 /* StatusCode.swift */; }; @@ -52,6 +54,7 @@ 30C77CB4266AF47300A888DC /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB3266AF47300A888DC /* Credentials.swift */; }; 30C77CB6266AF48300A888DC /* CurrentUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB5266AF48300A888DC /* CurrentUser.swift */; }; 30C77CB8266BD44300A888DC /* CreatePostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C77CB7266BD44300A888DC /* CreatePostViewController.swift */; }; + 30FBE8602690E26D00557F41 /* KeychainStoreServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FBE85F2690E26D00557F41 /* KeychainStoreServiceTest.swift */; }; 30FD0E722659645A006E309A /* Faker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FD0E712659645A006E309A /* Faker.swift */; }; E021984723FA35E00025C28E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984623FA35E00025C28E /* AppDelegate.swift */; }; E021984923FA35E00025C28E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E021984823FA35E00025C28E /* SceneDelegate.swift */; }; @@ -73,6 +76,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 3013628C269A91E50001580D /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = ""; }; + 301649F6268F967E00F26F4D /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = ""; }; 302B583D267E658E007133E6 /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = ""; }; 302B583E267E658E007133E6 /* AmacaConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmacaConfig.swift; sourceTree = ""; }; 302B583F267E658E007133E6 /* StatusCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusCode.swift; sourceTree = ""; }; @@ -118,6 +123,7 @@ 30C77CB3266AF47300A888DC /* Credentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; 30C77CB5266AF48300A888DC /* CurrentUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentUser.swift; sourceTree = ""; }; 30C77CB7266BD44300A888DC /* CreatePostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePostViewController.swift; sourceTree = ""; }; + 30FBE85F2690E26D00557F41 /* KeychainStoreServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStoreServiceTest.swift; sourceTree = ""; }; 30FD0E712659645A006E309A /* Faker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Faker.swift; sourceTree = ""; }; E021984323FA35E00025C28E /* Secretly.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Secretly.app; sourceTree = BUILT_PRODUCTS_DIR; }; E021984623FA35E00025C28E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -207,9 +213,11 @@ isa = PBXGroup; children = ( 30BC8BA32662BDEF00F7E6A5 /* ImageStore.swift */, + 301649F6268F967E00F26F4D /* KeychainStore.swift */, 30BC8BA52662C02300F7E6A5 /* CacheImage.swift */, 30BC8BA12662BB0000F7E6A5 /* DataContainer.swift */, 30BC8B9F2662B8A700F7E6A5 /* StorageType.swift */, + 3013628C269A91E50001580D /* KeychainError.swift */, ); path = Storage; sourceTree = ""; @@ -282,6 +290,7 @@ children = ( E021985D23FA35E20025C28E /* SecretlyTests.swift */, E021985F23FA35E20025C28E /* Info.plist */, + 30FBE85F2690E26D00557F41 /* KeychainStoreServiceTest.swift */, ); path = SecretlyTests; sourceTree = ""; @@ -411,6 +420,7 @@ E021984B23FA35E00025C28E /* WelcomeViewController.swift in Sources */, 3072FBDF2680FA5A00B35C8C /* ImageProcessor.swift in Sources */, 302BB622267E38E800FD74F5 /* PostInputViewController+UIImagePickerControllerDelegate.swift in Sources */, + 3013628D269A91E50001580D /* KeychainError.swift in Sources */, 302B5845267E658E007133E6 /* HttpResponse.swift in Sources */, 302B584A267E658E007133E6 /* RestClient.swift in Sources */, 302B5848267E658E007133E6 /* HttpClient.swift in Sources */, @@ -430,6 +440,7 @@ 3033795D267537B40066D94A /* FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift in Sources */, 30BC8BA82662CEBA00F7E6A5 /* Checksum.swift in Sources */, 30FD0E722659645A006E309A /* Faker.swift in Sources */, + 301649F7268F967E00F26F4D /* KeychainStore.swift in Sources */, 30337957267536E30066D94A /* FeedCollectionViewController+UICollectionViewDelegate.swift in Sources */, 307A305E2661CD510020DF8B /* PostCollectionViewCell.swift in Sources */, 302BB626267E447900FD74F5 /* PostInputViewController+UITextFieldDelegate.swift in Sources */, @@ -461,6 +472,7 @@ buildActionMask = 2147483647; files = ( E021985E23FA35E20025C28E /* SecretlyTests.swift in Sources */, + 30FBE8602690E26D00557F41 /* KeychainStoreServiceTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Secretly/Models/CurrentUser.swift b/Secretly/Models/CurrentUser.swift index fb28cfe..818bb98 100644 --- a/Secretly/Models/CurrentUser.swift +++ b/Secretly/Models/CurrentUser.swift @@ -10,7 +10,7 @@ import Foundation class CurrentUser { static func load() -> CurrentUser? { - guard let username = UserDefaults.standard.string(forKey: "secretly.username") else { + guard let username = try? KeychainStore.common.getItem(forKey: "secretly.username") else { return nil } return CurrentUser(username: username) @@ -20,7 +20,7 @@ class CurrentUser { init(username: String) { self.username = username - UserDefaults.standard.set(username, forKey: "secretly.username") + _ = KeychainStore.common.setItem(key: "secretly.username", value: username) } func credentials() -> Credentials { @@ -32,12 +32,12 @@ class CurrentUser { } private func password() -> String? { - return UserDefaults.standard.string(forKey: "secretly.password") + return try? KeychainStore.common.getItem(forKey: "secretly.password") } private func genPassword() -> String { let newPsswd = UUID().uuidString - UserDefaults.standard.set(newPsswd, forKey: "secretly.password") + _ = KeychainStore.common.setItem(key: "secretly.password", value: newPsswd) return newPsswd } } diff --git a/Secretly/Network/AmacaConfig.swift b/Secretly/Network/AmacaConfig.swift index 173da18..4693fe3 100644 --- a/Secretly/Network/AmacaConfig.swift +++ b/Secretly/Network/AmacaConfig.swift @@ -19,12 +19,13 @@ struct AmacaConfig { var apiToken: String? { get { - UserDefaults.standard.string(forKey: "amaca.apitoken") + try? KeychainStore.common.getItem(forKey: "amaca.apitoken") + } } func setApiToken(_ value: String) { - UserDefaults.standard.set(value, forKey: "amaca.apitoken") + _ = KeychainStore.common.setItem(key: "amaca.apitoken", value: value) } private var filepath: String { diff --git a/Secretly/Storage/KeychainError.swift b/Secretly/Storage/KeychainError.swift new file mode 100644 index 0000000..381c455 --- /dev/null +++ b/Secretly/Storage/KeychainError.swift @@ -0,0 +1,15 @@ +// +// KeychainError.swift +// Secretly +// +// Created by Luis Abraham Ortega Gonzalez on 10/07/21. +// Copyright © 2021 3zcurdia. All rights reserved. +// + +import Foundation + +enum KeychainError: Error { + case noItem + case unexpectedItemData + case unhandledError(status: OSStatus) +} diff --git a/Secretly/Services/KeychainService.swift b/Secretly/Storage/KeychainStore.swift similarity index 91% rename from Secretly/Services/KeychainService.swift rename to Secretly/Storage/KeychainStore.swift index 4ab0e5e..8a9198a 100644 --- a/Secretly/Services/KeychainService.swift +++ b/Secretly/Storage/KeychainStore.swift @@ -9,30 +9,25 @@ import Foundation - - -enum KeychainError: Error { - case noItem - case unexpectedItemData - case unhandledError(status: OSStatus) -} - - - - - -struct KeychainService{ +struct KeychainStore{ public let serviceName:String? - private let defaultServiceName: String = { + private static let defaultServiceName: String = { return Bundle.main.bundleIdentifier ?? "com.secretly.ioslab" }() + public static let common = KeychainStore() + public init(serviceName:String? = nil){ self.serviceName = serviceName } + private init(){ + self.init(serviceName: KeychainStore.defaultServiceName) + } + + public func setItem(key :String, value: String, accesibility: CFString? = nil) -> Bool { let encodedValue = value.data(using: String.Encoding.utf8)! @@ -108,7 +103,7 @@ struct KeychainService{ public func deleteAllItems() -> Bool { var query = [String:Any]() query[kSecClass as String] = kSecClassInternetPassword - query[kSecAttrServer as String] = self.serviceName ?? self.defaultServiceName + query[kSecAttrServer as String] = self.serviceName ?? KeychainStore.defaultServiceName let status: OSStatus = SecItemDelete(query as CFDictionary) if status == errSecSuccess { @@ -125,7 +120,7 @@ struct KeychainService{ keychainQueryDictionary[kSecClass as String] = kSecClassInternetPassword keychainQueryDictionary[kSecAttrDescription as String] = encodedIdentifier keychainQueryDictionary[kSecAttrAccount as String] = encodedIdentifier - keychainQueryDictionary[kSecAttrServer as String] = self.serviceName ?? self.defaultServiceName + keychainQueryDictionary[kSecAttrServer as String] = self.serviceName ?? KeychainStore.defaultServiceName if let accessString = accesibility { keychainQueryDictionary[kSecAttrAccessible as String] = accessString diff --git a/SecretlyTests/KeychainServiceTest.swift b/SecretlyTests/KeychainStoreServiceTest.swift similarity index 85% rename from SecretlyTests/KeychainServiceTest.swift rename to SecretlyTests/KeychainStoreServiceTest.swift index 4d5f6c5..8315f83 100644 --- a/SecretlyTests/KeychainServiceTest.swift +++ b/SecretlyTests/KeychainStoreServiceTest.swift @@ -11,7 +11,7 @@ import XCTest @testable import Secretly -class KeychainServiceTest: XCTestCase { +class KeychainStoreTest: XCTestCase { let serviceName = "test.secretly" let testString = "testString" @@ -41,7 +41,7 @@ class KeychainServiceTest: XCTestCase { func testSaveValueToKeychain(){ let test = "string1" - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let result = keychain.setItem(key:test, value:test) @@ -50,7 +50,7 @@ class KeychainServiceTest: XCTestCase { func testReadValueFromKeyChain(){ - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let result = try? keychain.getItem(forKey: self.testString) @@ -60,7 +60,7 @@ class KeychainServiceTest: XCTestCase { func testUpdateValueInKeychain(){ let value = "test2" - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let updated = keychain.setItem(key: self.testString, value: value) @@ -72,7 +72,7 @@ class KeychainServiceTest: XCTestCase { func testReadNoValueFromKeyChain(){ - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let key = "test2" XCTAssertThrowsError(try keychain.getItem(forKey: key)) { @@ -87,7 +87,7 @@ class KeychainServiceTest: XCTestCase { func testDeleteAnItemFromKeyChain(){ - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let result = keychain.deleteItem(forKey:self.testString) @@ -97,7 +97,7 @@ class KeychainServiceTest: XCTestCase { func testDeleteAllValuesFromKeyChain(){ - let keychain = KeychainService(serviceName:self.serviceName) + let keychain = KeychainStore(serviceName:self.serviceName) let result = keychain.deleteAllItems() XCTAssertTrue(result) From 3283b88f2db65d09ba3b746faccf99a847ba1c13 Mon Sep 17 00:00:00 2001 From: abe Date: Tue, 13 Jul 2021 14:13:38 -0500 Subject: [PATCH 4/4] =?UTF-8?q?Se=20agregan=20consideraciones=20para=20que?= =?UTF-8?q?=20los=20Post=20puedan=20ser=20visualizados=20de=20mejor=20mane?= =?UTF-8?q?ra.=20Se=20arregla=20una=20parte=20del=20prefetch,=20el=20probl?= =?UTF-8?q?ema=20era=20el=20layout=20del=20collectionView,=20se=20agregaro?= =?UTF-8?q?n=20caracter=C3=ADsticas=20manualmente=20y=20ahora=20la=20aplic?= =?UTF-8?q?aci=C3=B3n=20ya=20invoca=20al=20m=C3=A9todo=20correspondiente.?= =?UTF-8?q?=20Sin=20embargo,=20no=20supe=20si=20la=20API=20recib=C3=ADa=20?= =?UTF-8?q?par=C3=A1metros=20para=20limitar=20los=20enviados.=20To=20do:?= =?UTF-8?q?=20Implementar=20por=20completo=20el=20prefetch=20para=20cargar?= =?UTF-8?q?=20los=20post=20futuros=20y=20hacer=20el=20infinity=20scroll.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Secretly/Base.lproj/Main.storyboard | 2 +- Secretly/Network/AmacaConfig.swift | 2 +- ...ionViewController+UICollectionViewDataSource .swift | 1 + ...ontroller+UICollectionViewDelegateFlowLayout .swift | 10 +++++++++- .../ViewControllers/FeedCollectionViewController.swift | 7 ++++++- Secretly/Views/PostCollectionViewCell.xib | 6 ++++++ 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Secretly/Base.lproj/Main.storyboard b/Secretly/Base.lproj/Main.storyboard index 4ed5b13..794742f 100644 --- a/Secretly/Base.lproj/Main.storyboard +++ b/Secretly/Base.lproj/Main.storyboard @@ -80,7 +80,7 @@ - + diff --git a/Secretly/Network/AmacaConfig.swift b/Secretly/Network/AmacaConfig.swift index 4693fe3..4a3dae9 100644 --- a/Secretly/Network/AmacaConfig.swift +++ b/Secretly/Network/AmacaConfig.swift @@ -11,7 +11,7 @@ import Foundation struct AmacaConfig { static let shared = AmacaConfig() var host: String { - values["host"] as! String + return values["host"] as! String } var httpClient: HttpClient { HttpClient(session: URLSession.shared, baseUrl: host) diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift index 15100dd..98fa139 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDataSource .swift @@ -23,4 +23,5 @@ extension FeedCollectionViewController: UICollectionViewDataSource { cell.post = self.posts?[indexPath.row] return cell } + } diff --git a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift index e6c7ccc..b841a37 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift +++ b/Secretly/ViewControllers/FeedCollectionViewController+UICollectionViewDelegateFlowLayout .swift @@ -9,7 +9,15 @@ import UIKit extension FeedCollectionViewController: UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: collectionView.bounds.width, height: 300) + let numberOfItemsPerRow:CGFloat = 1 + let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout + + let totalSpacing = Int(flowLayout.sectionInset.left) + Int(flowLayout.sectionInset.right) + Int((numberOfItemsPerRow-1) * flowLayout.minimumInteritemSpacing) + let width = (Int(collectionView.bounds.width) - totalSpacing)/Int(numberOfItemsPerRow) + return CGSize(width: width, height: 300) } + } diff --git a/Secretly/ViewControllers/FeedCollectionViewController.swift b/Secretly/ViewControllers/FeedCollectionViewController.swift index 9180d42..b6173f7 100644 --- a/Secretly/ViewControllers/FeedCollectionViewController.swift +++ b/Secretly/ViewControllers/FeedCollectionViewController.swift @@ -30,14 +30,19 @@ class FeedCollectionViewController: UIViewController { } func setupCollectionView() { + let layout = UICollectionViewFlowLayout() + layout.minimumLineSpacing = 4 + layout.minimumInteritemSpacing = 4 + layout.sectionInset = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) postInputView.delegate = self collectionView.delegate = self collectionView.dataSource = self collectionView.prefetchDataSource = self +// collectionView.isPrefetchingEnabled = true + collectionView.collectionViewLayout = layout let nib = UINib(nibName: String(describing: PostCollectionViewCell.self), bundle: nil) collectionView.register(nib, forCellWithReuseIdentifier: PostCollectionViewCell.reuseIdentifier) collectionView.addSubview(refreshControl) - refreshControl.addTarget(self, action: #selector(self.loadPosts), for: UIControl.Event.valueChanged) } diff --git a/Secretly/Views/PostCollectionViewCell.xib b/Secretly/Views/PostCollectionViewCell.xib index 7b3a4e2..513f15e 100644 --- a/Secretly/Views/PostCollectionViewCell.xib +++ b/Secretly/Views/PostCollectionViewCell.xib @@ -23,6 +23,12 @@ + + + + + +