kotlin - 无法从 Keychain 获取已保存的帐户

我使用 Apple Keychain 对 iOS 的客户经理做了一个简单的模拟:

@Serializable
data class Account(
    val name: String,
    val password: String,
    val accessToken: String,
    val refreshToken: String,
)  

actual class AccountManagerGateway : IAccountManagerGateway {
    private val json = Json { encodeDefaults = true }

    override fun addAccount(account: Account) = updateAccount(account)

    override fun updateAccount(account: Account) {
        val data = json.encodeToString(account)
        if (!saveData(data)) {
            throw AccountException("Account not saved")
        }
    }

    override fun getAccount(): Account? = this.getData()?.let {
        json.decodeFromString(it)
    }

    override fun deleteAccount() {
        deleteData()
    }

    private fun getData(): String? {
        memScoped {
            val query = query(
                kSecClass to kSecClassGenericPassword,
                kSecAttrService to SERVICE.toCFDict(),
                kSecAttrAccount to ACCOUNT_TYPE.toCFDict(),
            )
            val result = alloc<CFTypeRefVar>()
            val status = SecItemCopyMatching(query, result.ptr)
            if (status == errSecSuccess && result.value != null) {
                val value = CFBridgingRelease(result.value) as? NSData
                return value?.stringValue
            }
            return null
        }
    }

    private fun deleteData(): Boolean {
        memScoped {
            val query = query(
                kSecClass to kSecClassGenericPassword,
                kSecAttrService to SERVICE.toCFDict(),
                kSecAttrAccount to ACCOUNT_TYPE.toCFDict(),
            )
            val status = SecItemDelete(query)
            return status == errSecSuccess || status == errSecItemNotFound
        }
    }

    private fun saveData(value: String): Boolean {
        memScoped {
            val query = query(
                kSecClass to kSecClassGenericPassword,
                kSecAttrService to SERVICE.toCFDict(),
                kSecAttrAccount to ACCOUNT_TYPE.toCFDict(),
                kSecValueData to value.toCFDict(),
            )
            var status = SecItemAdd(query, null)
            if (status == errSecDuplicateItem) {
                SecItemDelete(query)
                status = SecItemAdd(query, null)
            }
            return status == errSecSuccess
        }
    }

    private fun query(vararg pairs: Pair<CValuesRef<*>?, CValuesRef<*>?>): CFMutableDictionaryRef? {
        memScoped {
            val dict = CFDictionaryCreateMutable(null, pairs.size.toLong(), null, null)
            pairs.forEach {
                CFDictionaryAddValue(dict, it.first, it.second)
            }
            return dict
        }
    }

    private fun String.toCFDict(): CFTypeRef? {
        memScoped {
            return CFBridgingRetain(
                NSData.dataWithBytes(
                    bytes = this@toCFDict.cstr.ptr,
                    length = this@toCFDict.length.toULong()
                )
            )
        }
    }

    private val NSData.stringValue: String?
        get() = NSString.create(this, NSUTF8StringEncoding) as String?

    companion object {
        const val ACCOUNT_TYPE = "com.myapp"
        const val SERVICE = "www.myapp.com"
    }
}

经理正确保存帐户,至少我认为是因为在第二次通话时我得到了 errSecDuplicateItem 状态。但是,当我调用 getAccount 时,它总是返回 null

我的代码有什么问题?

UPD

我改进了 getData() 一点:

private fun getData(): String? {
    memScoped {
        val query = query(
            kSecClass to kSecClassGenericPassword,
            kSecAttrService to SERVICE.toCFDict(),
            kSecAttrAccount to ACCOUNT_TYPE.toCFDict(),
        )
        val result = alloc<CFTypeRefVar>()
        val status = SecItemCopyMatching(query, result.ptr)
        when (status) {
            errSecSuccess -> {
                if (result.value != null) {
                    val value = CFBridgingRelease(result.value) as? NSData
                    return value?.stringValue
                } else {
                    throw AccountException(
                        "Error while searching for an account, result is null"
                    )
                }
            }
            errSecItemNotFound -> return null
        }
        throw AccountException("Error while searching for an account, status: `$status`")
    }
}

它崩溃并出现异常:Error while searching for an account, result is null,即搜索成功但结果是 null 虽然 saveData() 找到重复项......我不只是明白......

回答1

最后,我发现了我的错误。 SecItemCopyMatching 不会返回没有 kSecReturnData to kCFBooleanTrue 属性的任何结果。因此,最终方法如下所示:

private fun getData(): String? {
    memScoped {
        val query = query(
            kSecClass to kSecClassGenericPassword,
            kSecAttrService to SERVICE.toCFDict(),
            kSecAttrAccount to ACCOUNT_TYPE.toCFDict(),
            kSecReturnData to kCFBooleanTrue,
        )
        val result = alloc<CFTypeRefVar>()
        val status = SecItemCopyMatching(query, result.ptr)
        when (status) {
            errSecSuccess -> {
                if (result.value != null) {
                    val value = CFBridgingRelease(result.value) as? NSData
                    return value?.stringValue
                } else {
                    throw AccountException(
                        "Error while searching for an account, result is null"
                    )
                }
            }
            errSecItemNotFound -> return null
        }
        throw AccountException("Error while searching for an account, status: `$status`")
    }
}

相似文章

随机推荐

最新文章