Prechádzať zdrojové kódy

Merge pull request #478 from kishikawakatsumi/override-authentication-ui

Can override UseAuthenticationUI
Kishikawa Katsumi 5 rokov pred
rodič
commit
d2b527691f
2 zmenil súbory, kde vykonal 272 pridanie a 48 odobranie
  1. 168 12
      .gitignore
  2. 104 36
      Lib/KeychainAccess/Keychain.swift

+ 168 - 12
.gitignore

@@ -1,8 +1,20 @@
-# OS X
-.DS_Store
+### https://raw.github.com/github/gitignore/2a4de265d37eca626309d8e115218d18985b5435/Swift.gitignore
 
 # Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
 build/
+DerivedData/
+*.moved-aside
 *.pbxuser
 !default.pbxuser
 *.mode1v3
@@ -11,18 +23,162 @@ build/
 !default.mode2v3
 *.perspectivev3
 !default.perspectivev3
-xcuserdata
-*.xccheckout
-profile
-*.moved-aside
-DerivedData
+
+## Obj-C/Swift specific
 *.hmap
+
+## App packaging
 *.ipa
-*.xcuserstate
+*.dSYM.zip
+*.dSYM
+
+## Playgrounds
+timeline.xctimeline
+playground.xcworkspace
 
-# Bundler
-.bundle
-vendor/
+# Swift Package Manager
+#
+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
+# Packages/
+# Package.pins
+# Package.resolved
+# *.xcodeproj
+#
+# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
+# hence it is not needed unless you have added a package configuration file to your project
+# .swiftpm
+
+.build/
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+# *.xcworkspace
 
 # Carthage
-Carthage/Build
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build/
+
+# Accio dependency management
+Dependencies/
+.accio/
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
+
+
+### https://raw.github.com/github/gitignore/2a4de265d37eca626309d8e115218d18985b5435/Global/macOS.gitignore
+
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon

+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### https://raw.github.com/github/gitignore/2a4de265d37eca626309d8e115218d18985b5435/Ruby.gitignore
+
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+
+# Used by dotenv library to load environment variables.
+# .env
+
+# Ignore Byebug command history file.
+.byebug_history
+
+## Specific to RubyMotion:
+.dat*
+.repl_history
+build/
+*.bridgesupport
+build-iPhoneOS/
+build-iPhoneSimulator/
+
+## Specific to RubyMotion (use of CocoaPods):
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# vendor/Pods/
+
+## Documentation cache and generated files:
+/.yardoc/
+/_yardoc/
+/doc/
+/rdoc/
+
+## Environment normalization:
+/.bundle/
+/vendor/bundle
+/lib/bundler/man/
+
+# for a library or gem, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# Gemfile.lock
+# .ruby-version
+# .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
+
+# Used by RuboCop. Remote config files pulled in from inherit_from directive.
+# .rubocop-https?--*
+
+

+ 104 - 36
Lib/KeychainAccess/Keychain.swift

@@ -153,16 +153,66 @@ public enum Accessibility {
     case alwaysThisDeviceOnly
 }
 
+/**
+ Predefined item attribute constants used to get or set values
+ in a dictionary. The kSecUseAuthenticationUI constant is the key and its
+ value is one of the constants defined here.
+ If the key kSecUseAuthenticationUI not provided then kSecUseAuthenticationUIAllow
+ is used as default.
+ */
+public enum AuthenticationUI {
+    /**
+     Specifies that authenticate UI can appear.
+     */
+    case allow
+
+    /**
+     Specifies that the error
+     errSecInteractionNotAllowed will be returned if an item needs
+     to authenticate with UI
+     */
+    case fail
+
+    /**
+     Specifies that all items which need
+     to authenticate with UI will be silently skipped. This value can be used
+     only with SecItemCopyMatching.
+     */
+    case skip
+}
+
+@available(iOS 9.0, OSX 10.11, *)
+extension AuthenticationUI {
+    public var rawValue: String {
+        switch self {
+        case .allow:
+            return UseAuthenticationUIAllow
+        case .fail:
+            return UseAuthenticationUIFail
+        case .skip:
+            return UseAuthenticationUISkip
+        }
+    }
+
+    public var description: String {
+        switch self {
+        case .allow:
+            return "allow"
+        case .fail:
+            return "fail"
+        case .skip:
+            return "skip"
+        }
+    }
+}
+
 public struct AuthenticationPolicy: OptionSet {
     /**
      User presence policy using Touch ID or Passcode. Touch ID does not
      have to be available or enrolled. Item is still accessible by Touch ID
      even if fingers are added or removed.
      */
-    @available(iOS 8.0, *)
-    @available(OSX 10.10, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 8.0, *)
+    @available(iOS 8.0, OSX 10.10, watchOS 2.0, tvOS 8.0, *)
     public static let userPresence = AuthenticationPolicy(rawValue: 1 << 0)
 
     /**
@@ -170,10 +220,7 @@ public struct AuthenticationPolicy: OptionSet {
      at least one finger must be enrolled. With Face ID user has to be enrolled. Item is still accessible by Touch ID even
      if fingers are added or removed. Item is still accessible by Face ID if user is re-enrolled.
      */
-    @available(iOS 11.3, *)
-    @available(OSX 10.13.4, *)
-    @available(watchOS 4.3, *)
-    @available(tvOS 11.3, *)
+    @available(iOS 11.3, OSX 10.13.4, watchOS 4.3, tvOS 11.3, *)
     public static let biometryAny = AuthenticationPolicy(rawValue: 1 << 1)
 
     /**
@@ -189,10 +236,7 @@ public struct AuthenticationPolicy: OptionSet {
      Constraint: Touch ID from the set of currently enrolled fingers. Touch ID must be available and at least one finger must
      be enrolled. When fingers are added or removed, the item is invalidated. When Face ID is re-enrolled this item is invalidated.
      */
-    @available(iOS 11.3, *)
-    @available(OSX 10.13.4, *)
-    @available(watchOS 4.3, *)
-    @available(tvOS 11.3, *)
+    @available(iOS 11.3, OSX 10.13, watchOS 4.3, tvOS 11.3, *)
     public static let biometryCurrentSet = AuthenticationPolicy(rawValue: 1 << 3)
 
     /**
@@ -207,10 +251,7 @@ public struct AuthenticationPolicy: OptionSet {
     /**
      Constraint: Device passcode
      */
-    @available(iOS 9.0, *)
-    @available(OSX 10.11, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 9.0, *)
+    @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
     public static let devicePasscode = AuthenticationPolicy(rawValue: 1 << 4)
 
     /**
@@ -226,39 +267,27 @@ public struct AuthenticationPolicy: OptionSet {
      Constraint logic operation: when using more than one constraint,
      at least one of them must be satisfied.
      */
-    @available(iOS 9.0, *)
-    @available(OSX 10.12.1, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 9.0, *)
+    @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
     public static let or = AuthenticationPolicy(rawValue: 1 << 14)
 
     /**
      Constraint logic operation: when using more than one constraint,
      all must be satisfied.
      */
-    @available(iOS 9.0, *)
-    @available(OSX 10.12.1, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 9.0, *)
+    @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
     public static let and = AuthenticationPolicy(rawValue: 1 << 15)
 
     /**
      Create access control for private key operations (i.e. sign operation)
      */
-    @available(iOS 9.0, *)
-    @available(OSX 10.12.1, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 9.0, *)
+    @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
     public static let privateKeyUsage = AuthenticationPolicy(rawValue: 1 << 30)
 
     /**
      Security: Application provided password for data encryption key generation.
      This is not a constraint but additional item encryption mechanism.
      */
-    @available(iOS 9.0, *)
-    @available(OSX 10.12.1, *)
-    @available(watchOS 2.0, *)
-    @available(tvOS 9.0, *)
+    @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
     public static let applicationPassword = AuthenticationPolicy(rawValue: 1 << 31)
 
     #if swift(>=2.3)
@@ -432,6 +461,11 @@ public final class Keychain {
         return options.authenticationPrompt
     }
 
+    @available(iOS 9.0, OSX 10.11, *)
+    public var authenticationUI: AuthenticationUI {
+        return options.authenticationUI ?? .allow
+    }
+
     #if os(iOS) || os(OSX)
     @available(iOS 9.0, OSX 10.11, *)
     public var authenticationContext: LAContext? {
@@ -540,6 +574,13 @@ public final class Keychain {
         return Keychain(options)
     }
 
+    @available(iOS 9.0, OSX 10.11, *)
+    public func authenticationUI(_ authenticationUI: AuthenticationUI) -> Keychain {
+        var options = self.options
+        options.authenticationUI = authenticationUI
+        return Keychain(options)
+    }
+
     #if os(iOS) || os(OSX)
     @available(iOS 9.0, OSX 10.11, *)
     public func authenticationContext(_ authenticationContext: LAContext) -> Keychain {
@@ -633,14 +674,26 @@ public final class Keychain {
         query[AttributeAccount] = key
         #if os(iOS)
         if #available(iOS 9.0, *) {
-            query[UseAuthenticationUI] = UseAuthenticationUIFail
+            if let authenticationUI = options.authenticationUI {
+                query[UseAuthenticationUI] = authenticationUI.rawValue
+            } else {
+                query[UseAuthenticationUI] = UseAuthenticationUIFail
+            }
         } else {
             query[UseNoAuthenticationUI] = kCFBooleanTrue
         }
         #elseif os(OSX)
         query[ReturnData] = kCFBooleanTrue
         if #available(OSX 10.11, *) {
-            query[UseAuthenticationUI] = UseAuthenticationUIFail
+            if let authenticationUI = options.authenticationUI {
+                query[UseAuthenticationUI] = authenticationUI.rawValue
+            } else {
+                query[UseAuthenticationUI] = UseAuthenticationUIFail
+            }
+        }
+        #else
+        if let authenticationUI = options.authenticationUI {
+            query[UseAuthenticationUI] = authenticationUI.rawValue
         }
         #endif
 
@@ -789,17 +842,31 @@ public final class Keychain {
         if withoutAuthenticationUI {
             #if os(iOS) || os(watchOS) || os(tvOS)
             if #available(iOS 9.0, *) {
-                query[UseAuthenticationUI] = UseAuthenticationUIFail
+                if let authenticationUI = options.authenticationUI {
+                    query[UseAuthenticationUI] = authenticationUI.rawValue
+                } else {
+                    query[UseAuthenticationUI] = UseAuthenticationUIFail
+                }
             } else {
                 query[UseNoAuthenticationUI] = kCFBooleanTrue
             }
             #else
             if #available(OSX 10.11, *) {
-                query[UseAuthenticationUI] = UseAuthenticationUIFail
+                if let authenticationUI = options.authenticationUI {
+                    query[UseAuthenticationUI] = authenticationUI.rawValue
+                } else {
+                    query[UseAuthenticationUI] = UseAuthenticationUIFail
+                }
             } else if #available(OSX 10.10, *) {
                 query[UseNoAuthenticationUI] = kCFBooleanTrue
             }
             #endif
+        } else {
+            if #available(iOS 9.0, OSX 10.11, *) {
+                if let authenticationUI = options.authenticationUI {
+                    query[UseAuthenticationUI] = authenticationUI.rawValue
+                }
+            }
         }
         
         let status = SecItemCopyMatching(query as CFDictionary, nil)
@@ -1153,6 +1220,7 @@ struct Options {
     var comment: String?
 
     var authenticationPrompt: String?
+    var authenticationUI: AuthenticationUI?
     var authenticationContext: AnyObject?
 
     var attributes = [String: Any]()