I am really puzzled by this.
I have a class which initializes like this:
class MyClass {
let storage = Storage.sharedService
static let sharedInstance = MyClass()
fileprivate init() {
storage.dumpKeychain()
v1 = storage.loadNonimportantValue()
print("V1: \(v1 ?? "nil")")
v2 = storage.loadImportantValue()
print("V2: \(v2 ?? "nil")")
}
}
storage.dumpKeychain() is function (taken from internet) that prints content of the Keychain accessible to my app
func dumpKeychain() {
let query: [String: Any] = [
kSecClass as String : kSecClassGenericPassword,
kSecReturnData as String : kCFBooleanFalse!,
kSecReturnAttributes as String : kCFBooleanTrue!,
kSecReturnRef as String : kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
var values = [String:String]()
if lastResultCode == noErr {
let array = result as? Array<Dictionary<String, Any>>
for item in array! {
if let key = item[kSecAttrAccount as String] as? String, let value = item[kSecValueData as String] as? Data {
values[key] = String(data: value, encoding:.utf8)
}
}
}
print(values)
}
And storage.loadImportantValue() is function that loads a value from given kSecAttrAccount ("Important")
fileprivate func loadImportantValue() -> String? {
var readQuery : [String : Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "Important",
kSecReturnData as String: kCFBooleanTrue!
]
var storedData : AnyObject?
_ = SecItemCopyMatching(readQuery as CFDictionary, &storedData);
return String(data: storedData as? Data, encoding: String.Encoding.utf8)
}
What I see in the logs is that dumpKeychain returns:
[..., "Nonimportant": "correct value", "Important": "correct value", ...]
And the line print(value) in MyClass initializer prints:
V1: "correct value"
V2: "incorrect value"
How is it possible that these two calls made almost one after another return two different values from the same spot in Keychain for V2 and at the same time V1 is ok?
Thanks
Ok, I found the problem.
There really are two different values of the data.
This class is part of a framework which after updating was doing this. It is so that the prior version of framework was using another framework to manage keychain and that framework stored data with more attributes then my new version. And what I thought was replacing the data was actually adding new entry with less attributes.
So at the end after running the legacy code and then updating it with newer version there were two entries of the data at the "same" key.
Related
I have been working in iOS autofill credential extension since long days. I have checked so many iOS articles and videos. But I am not able to show credential on quick type bar, reset things are successfully integrated. Can any give me quick help?
Using this video and url which was shared from apple:
https://developer.apple.com/videos/play/wwdc2018/721
https://developer.apple.com/documentation/authenticationservices
I am using below code to save credential to Keychain for particular domain.
let keychain = Keychain(server: "instagram.com", protocolType: .https, authenticationType: .htmlForm)
keychain["emailAddress"] = "Password"
And use this code for save domain:
func savedomain(domain: String, account: String, password: String, completion: ((Bool, SharedWebCredentialsManagerError?) -> Void)? = nil) {
SecAddSharedWebCredential(domain as CFString, account as CFString, password as CFString?) { error in
guard let error = error else {
completion?(true, nil)
return
}
let errorDescription = CFErrorCopyDescription(error) as String
let saveFailedError = SharedWebCredentialsManagerError.saveFailed(errorDescription)
completion?(false, saveFailedError)
}
}
I have created autofill extension and getting saved credentials, but not able to display credential on quick type bar in safari for instagram.com
I have implemented autofill extension for all social sites, Sharing my source code to save emailID-Password with domain.
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Above "Save" function i have written in my custom KeyChainManager class, Also i added below code in KeyChainManager class which is as below.
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
I am saving my data from VC by calling our KeyChainManager class like below:
let email = (txtEmail?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let password = (txtPassword?.text ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
let domain = (txtDomain?.text ?? "").lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
let user = User(email: email, password: password, key: self.key, domain: domain, identifier: self.keyIdentifier)
let data = (try? JSONEncoder().encode(user)) ?? Data()
let _ = KeyChain.save(key: "\(self.keyIdentifier)", data: data)
This all stuff is for saving our credentials, Now major point is how to list all saved credentials in our extension's CredentialProviderViewController.swift.
For that i added below method in KeyChainManager class :
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
And calling this function from extension's CredentialProviderViewController.swift like this:
users.removeAll()
for i in 0..<1000000 {
if let encodedData = KeyChain.load(key: "\(i)") {
let user = try! JSONDecoder().decode(User.self, from: encodedData)
if user.key == key && ((serviceIdentifier.contains(user.domain ?? "")) ) {
users.append(user)
}
}else{
break
}
}
I hope this content helps you as i spent many days to create just one demo :) :)
Comment below, If its helps you :) :)
Thanks,
Anjali.
You need to populate ASCredentialIdentityStore in order for the quicktype bar to work. See the description of ASCredentialProviderViewController:
Optionally add ASPasswordCredentialIdentity instances to the shared ASCredentialIdentityStore to make identities available directly in the QuickType bar.
This is also described in the WWDC presentation you reference.
How I implemented population of ASCredentialIdentityStore:
var firstItem = ASPasswordCredentialIdentity(serviceIdentifier: ASCredentialServiceIdentifier(identifier: "https://online.somebank.com/auth/login", type: .URL), user: "login#bank.com", recordIdentifier: nil)
ASCredentialIdentityStore.shared.replaceCredentialIdentities(with: [firstItem]) { result, error in
if result {
print("saved")
}
}
In my case everything works perfect.
So I fetch all passwords from remote and then populate ASCredentialIdentityStore with available passwords.
I'm trying to generate RSA keys and save it to the keychain. I generate two public keys and one private key.
public key one -> generated using SecKeyGeneratePair &
private key one -> generated using SecKeyGeneratePair
public key two -> generated using SecKeyCreateWithData
for all three keys kSecAttrIsPermanent property set to true.
then I'm retrieving keys with below method
func GetKeysfromKeyChain(tag: String) -> SecKey? {
let query : [String: Any] = [
String(kSecClass) : kSecClassKey,
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrApplicationTag): tag,
String(kSecReturnRef): true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecSuccess {
print("key existed :")
return result as! SecKey?
}
return nil
}
with above method, can successfully get public key one and privat key one. but for public key two it always returns a nil value. no idea .hope your help with this.
this is how I generate public key two
let data2 = Data.init(base64Encoded: serverPublicKey)
let keyDict:[NSObject:NSObject] = [
kSecAttrIsPermanent: true as NSObject,
kSecAttrApplicationTag: "com.marlonbrand.serverpublic".data(using: String.Encoding.utf8)! as NSObject,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits: 2048 as NSObject,
kSecAttrKeyClass: kSecAttrKeyClassPublic
]
let publickeysi = SecKeyCreateWithData(data2! as CFData, keyDict as CFDictionary, nil)
Have you tried setting the type of your keyDict to [String : Any] and doing the appropriate casts where you need them? If I had to guess, the casts to NSObject are probably messing it up.
So I am messing around with the iMessages Framework, and in order to send data to another user the data must be sent as a URLComponents URL. However I can't figure out how to send a dictionary to use as the messages.url value.
func createMessage(data: dictionary) {
/*
The Dictionary has 3 values, 1 string and two arrays.
let dictionary = ["title" : "Title Of Dictionary", "Array1" : [String](), "Array2" : [String]() ] as [String : Any]
*/
let components = URLComponents()
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "messages-layout.png")!
layout.imageTitle = "\(dictionary["title"])"
let message = MSMessage()
message.url = components.url!
message.layout = layout
self.activeConversation?.insert(message, completionHandler: { (error: Error?) in
print("Insert Message")
})
}
Does anybody know how I can send the dictionary values as URLQueryItems in the URLComponents to save as the message.url ?
PS: I was wondering wether it would be possible to create an extension for the URL to store the dictionary in, that is what I am unsuccessfully trying atm.
Here is a code snippet to convert dictionary to URLQueryItems:
let dictionary = [
"name": "Alice",
"age": "13"
]
func queryItems(dictionary: [String:String]) -> [URLQueryItem] {
return dictionary.map {
// Swift 3
// URLQueryItem(name: $0, value: $1)
// Swift 4
URLQueryItem(name: $0.0, value: $0.1)
}
}
var components = URLComponents()
components.queryItems = queryItems(dictionary: dictionary)
print(components.url!)
You can use the following URLComponents extension:
extension URLComponents{
var queryItemsDictionary : [String:Any]{
set (queryItemsDictionary){
self.queryItems = queryItemsDictionary.map {
URLQueryItem(name: $0, value: "\($1)")
}
}
get{
var params = [String: Any]()
return queryItems?.reduce([:], { (_, item) -> [String: Any] in
params[item.name] = item.value
return params
}) ?? [:]
}
}
}
And set the url components:
var url = URLComponents()
url.queryItemsDictionary = ["firstName" : "John",
"lastName" : "Appleseed"]
You can use iMessageDataKit library. It's a tiny, useful MSMessage extension for setting/getting Int, Bool, Float, Double, String and Array values for keys.
It makes setting and getting data really easy like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "user_id")
message.md.set(value: "john", forKey: "username")
message.md.set(values: ["joy", "smile"], forKey: "tags")
print(message.md.integer(forKey: "user_id")!)
print(message.md.string(forKey: "username")!)
print(message.md.values(forKey: "tags")!)
(Disclaimer: I'm the author of iMessageDataKit)
I have a Dictionary that contains values in such a way that some values are empty string.
let fbPostDic: [String: AnyObject] = [
"title":"",
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with":"",
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
I want to filter this dictionary such a way that new dictionary should just contains elements without empty strings.
let fbPostDic: [String: AnyObject] = [
"first_name”:”Ali“,
"last_name”:”Ahmad“,
"nick_name”:”ahmad”,
"gender":1,
"gender_visibility":2,
"dob":"1985-08-25",
"dob_visibility":2,
"city":"Lahore",
"city_visibility":2,
"bio”:”Its bio detail.”,
"bio_visibility":2,
"nationality":"Pakistan",
"nationality_visibility":2,
"relationship_status”:”Single”,
"rel_status_visibility":2,
"relation_with_visibility":2,
"social_media_source":"Facebook",
]
There are present ways like
let keysToRemove = dict.keys.array.filter { dict[$0]! == nil }
But its support iOS9.0 or above. I want to give support of iOS8.0 as well.
Any suggestions?
Because the above dict is a constant, add an extra init method in Dictionary extension can simplify the process:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
init(_ elements: [Element]){
self.init()
for (key, value) in elements {
self[key] = value
}
}
}
print(Dictionary(dict.filter { $1 as? String != "" }))
However, if above dict can be declared as a variable. Probably try the one below without above extra Dictionary extension:
var dict: [String : AnyObject] = [...]
dict.forEach { if $1 as? String == "" { dict.removeValueForKey($0) } }
print(dict)
This solution will also work; But Allen's solution is more precise.
public class func filterDictionaryFromEmptyString(var dictionary:[String: AnyObject]) -> [String: AnyObject]
{
print(dictionary.keys.count)
for key:String in dictionary.keys
{
print(key)
print(dictionary[key]!)
if String(dictionary[key]!) == ""
{
dictionary.removeValueForKey(key)
}
}
print(dictionary.keys.count)
return dictionary
}
I have some optional properties (a small sample of the actual properties in the class, for brevity):
var assigneeIds: [Int]?
var locationIds: [Int]?
var searchQuery: String?
I need to convert these to JSON. I've been advised by the maintainer of the API to not supply keys with nil values, so my toJson method looks like this:
var result: [String: AnyObject] = [:]
if let assigneeIds = assigneeIds {
result["assignee_ids"] = assigneeIds
}
if let locationIds = locationIds {
result["location_ids"] = locationIds
}
if let searchQuery = searchQuery {
result["search_query"] = searchQuery
}
return result
This doesn't feel very "Swift" - it's quite verbose. Ideally, I'd like it to look similar to the following - with the exception of it not setting the key if the optional has no value. The below code will still set the key, along with an NSNull value which gets serialised to "assignee_ids": null in the resulting JSON. Presumably I can configure the serializer to omit NSNull values, but I've encountered this pattern elsewhere and am interested to see if there's a better way.
return [
"assignee_ids" : self.assigneeIds ?? NSNull(),
"location_ids" : self.locationIds ?? NSNull(),
"search_query" : self.searchQuery ?? NSNull()
]
How can I skip creating key->NSNull entries when building an inline dictionary in this way?
You actually don't need to unwrap the values at all, since the value won't be set in the dictionary when it's nil. Example:
var dict : [String : Any] = [:]
dict["a"] = 1 // dict = ["a" : 1]
dict["b"] = nil // dict = ["a" : 1]
dict["a"] = nil // dict = [:]
You can also CMD-Click on the bracket and look at the definition of the Dictionary subscript:
public subscript (key: Key) -> Value?
this means you get an Optional (whether it exists or not) and you set an Optional (to set it or remove it)
Your code will look like this:
var result: [String: AnyObject] = [:]
result["assignee_ids"] = assigneeIds
result["location_ids"] = locationIds
result["search_query"] = searchQuery
return result
Try this:
return [
"assignee_ids" : self.assigneeIds ?? NSNull(),
"location_ids" : self.locationIds ?? NSNull(),
"search_query" : self.searchQuery ?? NSNull()
].filter { key, val -> Bool in !(val is NSNull) }