-
Notifications
You must be signed in to change notification settings - Fork 2
/
touchid_secure.py
73 lines (66 loc) · 2.88 KB
/
touchid_secure.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# coding: utf-8
# from @omz https://gist.github.com/omz/66a763a9db15dc847690
from objc_util import *
import threading
NSBundle = ObjCClass('NSBundle')
LocalAuthentication = NSBundle.bundleWithPath_('/System/Library/Frameworks/LocalAuthentication.framework')
LocalAuthentication.load()
LAContext = ObjCClass('LAContext')
# authenticate() will raise one of these exceptions when authentication
# fails. They all derive from AuthFailedException, so you can catch that
# if you don't care about the failure reason, but you could also handle
# cancellation differently, for example.
class AuthFailedException (Exception): pass
class AuthCancelledException (AuthFailedException): pass
class AuthTimeoutException (AuthFailedException): pass
class AuthNotAvailableException (AuthFailedException): pass
class AuthFallbackMechanismSelectedException (AuthFailedException): pass
def is_available():
'''Return True if TouchID authentication is available, False otherwise'''
context = LAContext.new().autorelease()
return bool(context.canEvaluatePolicy_error_(1, None))
def authenticate(reason='', allow_passcode=True, timeout=None):
'''Authenticate the user via TouchID or passcode. Returns True on success, raises AuthFailedException (or a subclass) otherwise.'''
if not is_available():
raise AuthNotAvailableException('Touch ID is not available.')
policy = 2 if allow_passcode else 1
context = LAContext.new().autorelease()
event = threading.Event()
result = {}
def callback(_cmd, success, _error):
result['success'] = success
if _error:
error = ObjCInstance(_error)
result['error'] = error
event.set()
handler = ObjCBlock(callback, restype=None, argtypes=[c_void_p, c_bool, c_void_p])
context.evaluatePolicy_localizedReason_reply_(policy, reason, handler)
if not event.wait(timeout):
#NOTE: invalidate() is a private method (there's apparently no public API to cancel the TouchID dialog)
context.invalidate()
raise AuthTimeoutException('Timeout')
success = result.get('success', False)
error = result.get('error')
if success:
return True
elif error:
error_code = error.code()
if error_code == -2:
raise AuthCancelledException('Cancelled by user')
elif error_code == -3:
raise AuthFallbackMechanismSelectedException('Fallback authentication mechanism selected')
else:
desc = error.localizedDescription() or 'Unknown error'
raise AuthFailedException(desc)
else:
raise AuthFailedException('Unknown error')
# Demo:
def main():
try:
reason = 'We need you fingerprint to ste...ehm... to log you in. You have 10 seconds.'
authenticate(reason, allow_passcode=True, timeout=10)
print('Success!')
except AuthFailedException as e:
print(e)
if __name__ == '__main__':
main()