matthewpalmer / Locksmith

A powerful, protocol-oriented library for working with the keychain in Swift.
MIT License
2.92k stars 268 forks source link

Not able to downgrade the LocksmithAccessibleOption from "unlock" to "afterFirstUnlock" #190

Open ghost opened 6 years ago

ghost commented 6 years ago

In earlier version of my app the LocksmithAccessibleOption was default. Now I want to change it to "afterFirstUnlock". for that I added following code in my protocol var accessible: LocksmithAccessibleOption? {return .afterFirstUnlock}

But after using this I'm not able to either delete or access the keychain item at all. Help me here please.

rain-bason commented 6 years ago

I encounter this also. What I notice is that the keychain that was stored using the "unlock" accessible option can't be access or delete when you change to "afterFirstUnlock"

What I did is first check if data already exists using the "afterFirstUnlock" then if not I'll pull back by checking using the "unlock" accessible options. If the data is stored in the "unlock" accessible options I'll copy it over to "afterFirstUnlock" and delete the old data to avoid redundancy

ghost commented 6 years ago

"first check if data already exists using the "afterFirstUnlock" then if not I'll pull back by checking using the "unlock" accessible options. If the data is stored in the "unlock" accessible options I'll copy it over to "afterFirstUnlock" and delete the old data to avoid redundancy" -- > How I'm suppose to handle that. I'm using the protocol based approach. I'm attaching my code and please let me know how I can handle that:

private let UsernameKey = "com.xxxxx.usernameKey" private let KeyKey = "com.xxxxx.keyKey" private let Service = "xxxxx" private let AuthAccount = "xxxxxx-Authorization"

struct Autorization {

let username: String
let key: String

private struct Access: ReadableSecureStorable, DeleteableSecureStorable, GenericPasswordSecureStorable {
    //var accessible: LocksmithAccessibleOption? {return .afterFirstUnlock}
    var service: String { return Service }
    var account: String { return AuthAccount }
}

}

extension Autorization {

static func readCurrentAuthFromKeychain() -> Autorization? {
    let access = Access()
    let result = access.readFromSecureStore()
    guard let data = result?.data, let username = data[UsernameKey] as? String, let key = data[KeyKey] as? String else { return nil }
    return Autorization(username: username, key: key)
}

static func deleteCurrentUserFromKeychain() throws {
    let access = Access()
    try access.deleteFromSecureStore()
}

}

extension Autorization: CreateableSecureStorable { var data: [String : Any] { return [ UsernameKey: username as AnyObject, KeyKey: key as AnyObject ] }

}

extension Autorization: GenericPasswordSecureStorable { //var accessible: LocksmithAccessibleOption? {return .afterFirstUnlock} var service: String { return Service } var account: String { return AuthAccount } }

rain-bason commented 6 years ago

I'm not sure how can I change your code, but here is part of my code:

In a helper file:

private let accessible = LocksmithAccessibleOption.afterFirstUnlock
private func loadData(forUsername username: String) -> [String: Any]? {
    // case1: try if we can get the value when using "accessibleOption"
    // case2: try using the default method, if we can find value 
    // case3: we didn't have saved data for this username
    if let dataTemp = Locksmith.loadDataForUserAccount(userAccount: username, accessibleOption: accessible) {
        return dataTemp
    } else if let dataTemp = Locksmith.loadDataForUserAccount(userAccount: username) {
        // yup we have values with the old "accessibleOption", get it and assign to dataTemp
        try! Locksmith.deleteDataForUserAccount(userAccount: username) // remove it so we don't have redundant values for this key
        try! Locksmith.updateData(data: dataTemp, forUserAccount: username, accessibleOption: accessible) // assign it to the new "accesibleOption"
        return dataTemp // return the value
    } else {
        return nil
    }
}

then I created an extension:

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()
    }
}

I'm not sure if this is the ideal way, but it does works the way we need it.

ghost commented 6 years ago

"else if let dataTemp = Locksmith.loadDataForUserAccount(userAccount: username)" statement returns nil even if there is data in the keychain for the same account.