Locksmith error: Locksmith.LocksmithError.interactionNotAllowed - swift4

we're using Locksmith to save user data for Keychain. In our end everything works as it should but for some reason we receive crashes with the error Locksmith.LocksmithError.interactionNotAllowed.
Follows the code where the crash happen:
func updateUserAccessToken(forAccount account: String, token: String) {
var userAccessToken = Locksmith.loadDataForUserAccount(userAccount: account) ?? [String: Any]()
userAccessToken[“token”] = token
try! Locksmith.updateData(data: userAccessToken, forUserAccount: account)
}
Why is the code above crashes for other users? 'til . now we can't replicate the said crash. Any help is very much appreciated. Thanks!
UPDATE:
So we finally able to replicate this crash, and it's because we're accessing the keychain while the device is locked. I found out you can change the Keychain's "accessibility option" but I'm not sure how to do it in Locksmith. Anybody?

I found out that if you're using a protocol base approach changing the accessibility option is much more easier. But unfortunately our app doesn't use it. So what I did was I created an extension as follow:
extension Locksmith {
fileprivate static func loadDataForUserAccount(userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) -> [String: Any]? {
struct ReadRequest: GenericPasswordSecureStorable, ReadableSecureStorable {
let service: String
let account: String
var accessible: LocksmithAccessibleOption?
}
let request = ReadRequest(service: service, account: userAccount, accessible: accessibleOption)
return request.readFromSecureStore()?.data
}
fileprivate static func updateData(data: [String: Any],
forUserAccount userAccount: String,
inService service: String = LocksmithDefaultService,
accessibleOption: LocksmithAccessibleOption) throws {
struct UpdateRequest: GenericPasswordSecureStorable, CreateableSecureStorable {
let service: String
let account: String
let data: [String: Any]
var accessible: LocksmithAccessibleOption?
}
let request = UpdateRequest(service: service, account: userAccount, data: data, accessible: accessibleOption)
return try request.updateInSecureStore()
}
}
NOTE: changing the "accessibility option" may loss your access to the data previously saved with the default "accessibility option". If you need those data you may need to handle it separately.

Related

Firebase Firestore: Encoder from custom structs not working

So I created a function in which I try to create a document in my Firestore in which user data is stored. But when upgrading my project to the Xcode 13.0 beta, the Firebase encoder has stopped working. Anyone else experiencing a similar problem?
My model looks like this:
struct User: Identifiable, Codable {
#DocumentID var id: String?
var auth_id: String?
var username: String
var email: String
enum CodingKeys: String, CodingKey {
case auth_id
case username
case email
}
}
And the call to Firebase like this:
let newUser = User(auth_id: idToken, username: name, email: email)
try await databasemodel.database.collection("users").document(idToken).setData(newUser)
The Document is created with this code doesn't exist yet, but that worked earlier.
Now when I compile I get the error: "Cannot convert value of type 'User' to expected argument type '[String : Any]'"
No other errors are displayed, and I'm pretty sure the rest of the code works as expected.
So I ran into a similar issue with Codables... I've made this little extension that's saved me. Maybe it works for you too :)
extension Encodable {
/// Convenience function for object which adheres to Codable to compile the JSON
func compile () -> [String:Any] {
guard let data = try? JSONEncoder().encode(self) else {
print("Couldn't encode the given object")
preconditionFailure("Couldn't encode the given object")
}
return (try? JSONSerialization
.jsonObject(with: data, options: .allowFragments))
.flatMap { $0 as? [String: Any] }!
}
}
You can then do
try await databasemodel.database.collection("users").document(idToken).setData(newUser.compile())
Note. I didn't test this. I'm simply providing the extension which solved my problem when faced with the same thing.
Just use Firestore.Encoder
extension Encodable {
var dictionary: [String: Any]? {
return try? Firestore.Encoder().encode(self)
}
}

Where does SecKeyCreateSignature get the key name for Keychain signing authorization dialog?

I have noticed a difference between certain keys in the Keychain with respect to how they appear in the Keychain signing dialog, and I cannot figure out why some are displayed a certain way while others are not.
Here is some test code to use identities in the Keychain to sign a sample bit of data.
func testCreateSignature() throws {
let query: [String: Any] = [kSecClass as String: kSecClassIdentity,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: false,
kSecReturnRef as String: true,
kSecReturnData as String: true]
var resultsRef: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &resultsRef)
guard status == errSecSuccess else { throw SecurityError.unhandledError(status: status) }
guard let results = resultsRef as? [[String:Any]] else {
throw SecurityError.unexpectedCertificateData
}
let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
var privateKey: SecKey!
for result in results {
let secIdentity = result[kSecValueRef as String] as! SecIdentity
try SecIdentityCopyPrivateKey(secIdentity, &privateKey).check()
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA1, data as CFData, &error)!
if let error = error {
throw error.takeRetainedValue()
}
print(signature)
}
}
When the code attempts to use one of the keys that Xcode installed for code signing, the resulting dialog looks like the following:
However, when the code attempts to use a key that I've installed, no matter what the label on the key in the Keychain is, it always looks like this:
When my app attempts to use a key to sign, I would like the user to see the name of the key the app wants to use, instead of just generic "privateKey", but I cannot find where this information might be stored on the key.
I have checked the kSecAttrLabel and kSecAttrApplicationLabel attributes of both identities and the private keys and cannot find the text that appears in the dialogs.
I found it. It is a property of the Access Control List of a Keychain item. See 'descriptor' param for SecAccessCreate.
If you do not specify a custom ACL when importing a key, it will default to "privateKey".
I was using SecPKCS12Import to import a .pfx file. I attempted to set the kSecImportExportAccess key in the options parameter to a custom SecAccess object, but it would always import with a default ACL.
I ended up refactoring the code to use SecItemImport instead to import the .pfx file and supplied a custom SecAccess instance:
static func importIdentity(contentsOf url: URL, password: String) throws {
let data = try Data.init(contentsOf: url)
var access: SecAccess!
try SecAccessCreate("License Key" as CFString, nil, &access).check()
var keychain: SecKeychain!
var outItems: CFArray?
let filename: CFString? = url.isFileURL ? url.lastPathComponent as CFString : nil
var inputFormat: SecExternalFormat = .formatPKCS12
var itemType: SecExternalItemType = .itemTypeAggregate
let unmanagedPassword = Unmanaged<AnyObject>.passRetained(password as AnyObject)
let unmanagedAccess = Unmanaged<SecAccess>.passRetained(access)
var params: SecItemImportExportKeyParameters = SecItemImportExportKeyParameters(version: UInt32(SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION),
flags: .importOnlyOne,
passphrase: unmanagedPassword,
alertTitle: nil,
alertPrompt: nil,
accessRef: unmanagedAccess,
keyUsage: nil,
keyAttributes: nil)
try SecKeychainCopyDefault(&keychain).check()
try SecItemImport(data as CFData, filename, &inputFormat, &itemType, [], &params, keychain, &outItems).check()
}
Importing the identity as above will result in "License Key" being shown in the signing dialog rather than "privateKey".

Alamofire request or Firebase query is not working in a non UIViewController Class

Imperial trying to perform a request to a website using alamofire and my problem is the following:
When I use the corresponding code in an ViewController cocoaTOuch class viewDidLoad() function everything works fine.(here is the code)
super.viewDidLoad()
let loginActionUrl = url
do{
let parameters = [
"p_user":user,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseJSON
{response in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
......
If I repeat the same code inside a private function on a swift (non cocoa touch) class, then,I have no response, While debugging it tries to perform the request task twice and then jumps out of the {response in code block.
The code is the following:
private func checkInWithAeA(withLogIn: String, password: String) -> (Bool){
var companyUSerRecognized: Bool = false
var startIndex: String.Index!
let loginActionUrl = url
do{
let parameters = [
"p_user" : withLogIn,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseJSON
{response in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
companyUSerRecognized = true
......
I don't now what is happening but is the second time I have the same problem. What I'm dong is trying to avoid to set up to much code in the viewController using other support classes, following best practices, but I already tried to do this with firebase, and I have the same problem, the query to the database only worked in UIViewcontroller classes (in certain) and now is the same, I am not able to obtain any result when I execute the code in the clean swift file.
Is there any kind of limitation on this. Why I cannot do anything like an alamofire request or a firebase query to the realtime database out of a UIViewController class?
Here I add some information:
var myConnectionController: ConnectionController = ConnectionController()
let (companyUSerRecognized, error) = myConnectionController.chekUserIDandPassWord(forTheCompany: self.companyName, logInName: self.companyLogIn, password: self.companyPassword)
This call to the ConnectionController class (that is a swift plain class) asks for a connexion to a web page. If the response is good, then a true is obtained and the process is continued.
The function called has a switch statement:
public func chekUserIDandPassWord(forTheCompany: String, logInName: String, password: String) -> (Bool, String){
var companyUSerRecognized: Bool!
var error: String!
switch forTheCompany {
case "(AEA)":
companyUSerRecognized = checkInWithAeA(withLogIn: logInName, password: password)
break
.....
This is what calls Check in With AeA. (The function I just mentioned before). What I want to is get the cookies of the connection in return check them and if they are good, true is returned.
I already have done this in the viewDidLoad() function of a ViewController, In fact I can parse the response with SwiftSoup, etc. But If I do it this way I am not able to do it.
Thanks again
I finally made up the solution by reviewing some bibliography. I did not notice that, any alamofire call opens a pipeline. That is, we are obviously talking about asynchronous operations. There are two ways to handle with this kind of operations in swift. One is the use of future objects. This option allows the continuation of the execution by substituting the results from the async call when they are ready. Depending on the situation this is not so good. The other is to make the execution wait for the response of the async call. This is done with a closure. I took this lastoption.
The closer is to be performed by using a completion handler function that is called at the end of the async call block to return any value you need from the async call. In this case. This is what I called completion
private func checkInWithAeA(completion: #escaping (Bool)-> Void){
let loginActionUrl = url1
let postLoginUrl = url2
let parameters = [
"p_user" : logInName,
"p_password": password
]
AF.request(loginActionUrl, method: .post, parameters: parameters).responseData
{(response) in
if let header = response.response?.allHeaderFields as? [String: String],
let responseUrl = response.request?.url{
let sessionCookies = HTTPCookie.cookies(withResponseHeaderFields: header, for: responseUrl)
let cookieToSend = sessionCookies[0]
//Debug
print(cookieToSend)
AF.session.configuration.httpCookieStorage?.setCookie(cookieToSend)
completion(true)
}else{
completion(false)
}
}
}
That's it. Hope it helps
BTW. I think that this is the same problem with the firebase queries.
Thanks!

How to write Unit Test for UserDefaults

I have 2 functions where one basically retrieves string in User Defaults and another writes the string in User Defaults. I'm not able to understand that should I do the unit test or no? and I'm pretty new to the concept for Unit testing.
I scoured the internet for testing user defaults but I was advised to mock the test in the end.
My question is If there is a way to test User Defaults what is the best way to do it?
Constants and Structs
let defaults = UserDefaults.standard
let defaultinformation = "ABCDEFG"
struct Keys {
static let Information = "Information"
}
Function where saves Information
func SetDefaultInformation() {
defaults.set(defaultinformation, forKey: Keys.Information)
}
Function where retrieves Information
func checkForInformation() -> String {
let Information = defaults.value(forKey: Keys.Information) as? String ?? ""
return Information
}
Thanks in Advance
should I do the unit test or no
No. You know what UserDefaults does and you know how it works and that it works. It is ridiculous to test Apple's code. Testing defaults.set is just as silly; you know exactly what it does and you know that it will do it.
What you want to test is your code: not your retrieval from UserDefaults per se, but your response to that retrieval. Give yourself methods such that you can see what you do when information is a String and what you do when information is nil. As you've been told, a trivial mock can supply the back end, playing the part of UserDefaults and giving back different sorts of result. Just don't let your tests involve the real UserDefaults.
class ViewModel {
func saveUserName(name: String, userDefaults: UserDefaults = UserDefaults.standard) {
userDefaults.set(name, forKey: "username")
}
}
class MockUserDefault: UserDefaults {
var persistedUserName: String? = nil
var persistenceKey: String? = nil
override func set(_ value: Any?, forKey defaultName: String) {
persistedUserName = value as? String
persistenceKey = defaultName
}
}
func testSavingUserName() {
let viewModel = ViewModel()
let mockUserDefaults = MockUserDefault()
viewModel.saveUserName(name: "Oliver", userDefaults: mockUserDefaults)
XCTAssertEqual("Oliver", mockUserDefaults.persistedUserName)
XCTAssertEqual("username", mockUserDefaults.persistenceKey)
}

Realm unmanaged object 'Realm accessed from incorrect thread.' Swift3

I know there are a lot discussion about 'Realm accessed from incorrect thread.', but my case a bit different, at least to my eyes...
So I have the following data structure
class User: Object, Mappable {
dynamic var name: String?
dynamic var photo: Photo?
}
class Photo: Object, Mappable {
dynamic var cover: CoverPhoto?
let photos = List<PhotoData>() // User uploaded photos, not cover
}
class CoverPhoto: Object, Mappable {
dynamic var id: String?
dynamic var user: String?
var photos = List<PhotoData>() // Different size of cover photo
}
class PhotoData : Object, Mappable {
let width = RealmOptional<Int>()
let height = RealmOptional<Int>()
dynamic var url : String?
}
I am using ObjectMapper with alamofire to map my realm objects received via rest response.
The use data is already managed and in realm db.
There is a functionality to change cover photo thus via rest receiving the new set of cover photo list and wanted to replace it.
For that reason I'm replacing it by first remove existing and appending new one.
The problem occurred when trying to access to new data which received via rest which supposed to not be a managed object, but getting exception mentioned in description.
The code which updating realm object is below and it called right after received response via alamofire, so no any DB actions in between
func updateCoverPhotos(userData: User, photo: Photo, onSuccess: ((Bool) -> Void)?, onError: ((NSError) -> Void)?) {
let userDataRef = ThreadSafeReference(to: userData)
let photos = photo.photos
DispatchQueue.global().async {
do {
let realm = try! Realm()
realm.refresh()
guard let user = realm.resolve(userDataRef) else {
return // user was deleted
}
try realm.write {
user.photo!.cover!.photos.removeAll()
user.photo!.cover!.photos.append(objectsIn: photos)
}
DispatchQueue.main.sync {
print(photoData) // Exception thrown 'Realm accessed from incorrect thread.'
onSuccess?(true)
}
} catch let error as NSError {
DispatchQueue.main.async {
onError?(error)
}
}
}
}
Could you please have a look and let me know what I'm doing wrong here ?
UPDATED
Mapping right after response, which contains new list of: PhotoData
return alamofireManager.request(url, method: .post,
parameters: parameters,
encoding: JSONEncoding.default,
headers: alamofireManager.buildHeaders())
.validate()
.responseObject(keyPath: "data") { (response: DataResponse<Photo>) in