SecRequestSharedWebCredential凭据包含“密码未保存”?

时间:2016-08-01 12:12:30

标签: ios objective-c keychain

我们通过以下功能检索任何已保存的密码:

SecRequestSharedWebCredential(NULL, NULL, ^(CFArrayRef credentials, CFErrorRef error) {
    if (!error && CFArrayGetCount(credentials)) {
        CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);
        if (credential > 0) {
            CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);
            NSString *username = CFDictionaryGetValue(credential, kSecAttrAccount);
            NSString *password = CFDictionaryGetValue(credential, kSecSharedPassword);
            dispatch_async(dispatch_get_main_queue(), ^{
                //Updates the UI here.
            });
        }
    }
});

问题是在IOS 9.3.3 iPhone 6 A1524上,我们收到一个名为“密码未保存”的条目提示。没有错误消息表明未找到密码。因为数组> 0,它用条目完成表单。

为什么会这样?我们认为如果您的授权域名下没有存储密码,则不会出现提示。

有什么建议吗?

谢谢。

2 个答案:

答案 0 :(得分:1)

我正在viewDidLoad()为我的Auth视图控制器检查这个。代码与上面的内容略有不同,从其他几个SO答案中收集。

斯威夫特3:

SecRequestSharedWebCredential(Configuration.webBaseFQDN as CFString, nil, { (credentials, error) in

    if let error = error {
        print("ERROR: credentials")
        print(error)
    }

    guard let credentials = credentials, CFArrayGetCount(credentials) > 0 else {
        // Did not find a shared web credential.
        return
    }

    guard CFArrayGetCount(credentials) == 1 else {
        // There should be exactly one credential.
        return
    }

    let unsafeCredential = CFArrayGetValueAtIndex(credentials, 0)
    let credential = unsafeBitCast(unsafeCredential, to: CFDictionary.self)

    let unsafeEmail = CFDictionaryGetValue(credential, Unmanaged.passUnretained(kSecAttrAccount).toOpaque())
    let email = unsafeBitCast(unsafeEmail, to: CFString.self) as String

    let unsafePassword = CFDictionaryGetValue(credential, Unmanaged.passUnretained(kSecSharedPassword).toOpaque())
    let password = unsafeBitCast(unsafePassword, to: CFString.self) as String

    if self.isValidEmail(email) && self.isValidPassword(password) {
        self.usedSharedWebCredentials = true
        self.doSignIn(email: email, password: password)
    }
})

isValidEmail(_:)isValidPassword(_:)末尾的额外检查会处理SecRequeestSharedWebCredential()在第一个凭据(电子邮件)中返回“未保存的密码”的情况。

希望有人可以解释为什么会这样,但如果没有,至少有一种方法来捕捉这种情况。

我还想补充一点,我已经看到了iOS 10.2.1

答案 1 :(得分:0)

我遇到了同样的问题,并想补充一点,“未保存的密码”中的空格不是真实的空格。不知道为什么,从CFString转换时可能有些奇怪。

无论哪种方式,由于Apple documentation仍在ObjC中,并且它们的安全性框架仍然是CoreFoundation繁重的,所以我认为最好发布我为共享Web编写的整个Swift 5代码凭证包装器。

它具有很好的错误管理逻辑(可以调整,因为您可能没有相同的ErrorBuilder API)。关于奇怪的空间,当从Xcode复制到StackOverflow时,它们变成了实空间,因此在String扩展中还有额外的逻辑。

从我所看到的内容来看,没有什么比这更好的了。

//
//  CredentialsRepository.swift
//  Created by Alberto De Bortoli on 26/07/2019.
//

import Foundation

public typealias Username = String
public typealias Password = String

public struct Credentials {
    public let username: Username
    public let password: Password
}

public enum GetCredentialsResult {
    case success(Credentials)
    case cancelled
    case failure(Error)
}

public enum SaveCredentialsResult {
    case success
    case failure(Error)
}

protocol CredentialsRepository {
    func saveCredentials(_ credentials: Credentials, completion: @escaping (SaveCredentialsResult) -> Void)
    func getCredentials(completion: @escaping (GetCredentialsResult) -> Void)
}
//
//  SharedWebCredentialsController.swift
//  Created by Alberto De Bortoli on 26/07/2019.
//

class SharedWebCredentialsController {

    let domain: String

    init(domain: String) {
        self.domain = domain
    }
}

extension SharedWebCredentialsController: CredentialsRepository {

    func getCredentials(completion: @escaping (GetCredentialsResult) -> Void) {
        SecRequestSharedWebCredential(domain as CFString, .none) { cfArrayCredentials, cfError in
            switch (cfArrayCredentials, cfError) {
            case (_, .some(let cfError)):
                let underlyingError = NSError(domain: CFErrorGetDomain(cfError) as String,
                                              code: CFErrorGetCode(cfError),
                                              userInfo: (CFErrorCopyUserInfo(cfError) as? Dictionary))
                let error = ErrorBuilder.error(forCode: .sharedWebCredentialsFetchFailure, underlyingError: underlyingError)
                DispatchQueue.main.async {
                    completion(.failure(error))
                }

            case (.some(let cfArrayCredentials), _):
                if let credentials: [[String: String]] = cfArrayCredentials as? [[String: String]], credentials.count > 0,
                    let entry = credentials.first,
                    // let domain = entry[kSecAttrServer as String]
                    let username = entry[kSecAttrAccount as String],
                    let password = entry[kSecSharedPassword as String] {
                    DispatchQueue.main.async {
                        if username.isValidUsername() {
                            completion(.success(Credentials(username: username, password: password)))
                        }
                        else {
                            let error = ErrorBuilder.error(forCode: .sharedWebCredentialsFetchFailure, underlyingError: nil)
                            completion(.failure(error))
                        }
                    }
                }
                fallthrough

            default:
                DispatchQueue.main.async {
                    completion(.cancelled)
                }
            }
        }
    }

    func saveCredentials(_ credentials: Credentials, completion: @escaping (SaveCredentialsResult) -> Void) {
        SecAddSharedWebCredential(domain as CFString, credentials.username as CFString, credentials.password as CFString) { cfError in
            switch cfError {
            case .some(let cfError):
                let underlyingError = NSError(domain: CFErrorGetDomain(cfError) as String,
                                              code: CFErrorGetCode(cfError),
                                              userInfo: (CFErrorCopyUserInfo(cfError) as? Dictionary))
                let error = ErrorBuilder.error(forCode: .sharedWebCredentialsSaveFailure, underlyingError: underlyingError)
                DispatchQueue.main.async {
                    completion(.failure(error))
                }
            default:
                DispatchQueue.main.async {
                    completion(.success)
                }
            }
        }
    }
}
extension String {

    fileprivate func isValidUsername() -> Bool {
        // https://stackoverflow.com/questions/38698565/secrequestsharedwebcredential-credentials-contains-passwords-not-saved
        // don't touch the 'Passwords not saved', the spaces are not what they seem (value copied from debugger)
        guard self != "Passwords not saved" else { return false }
        let containsAllInvalidWords = contains("Passwords") && contains("not") && contains("saved")
        return !containsAllInvalidWords
    }
}