- attachment:HotpTotp.py of OneTimePasswords
Attachment 'HotpTotp.py'
Download 1 import logging
2 import re
3
4 from roundup.exceptions import Reject
5
6 logger = logging.getLogger('detector')
7
8
9 def audit_2fa(db, cl, nodeid, newvalues):
10 '''Cases:
11
12 secret_2fa must be 32 characters from base32 alphabet [A-Z2-7]
13 or must be '-' (disabled).
14
15 If secret_2fa is '-' secret_counter must be set to -1 in same
16 transacation.
17 Any other secret_2fa change must set secret_counter to 0 in same
18 transacation.
19 (Note this may change if TOTP used. Value of -2 reserved for
20 counter value in TOTP mode.)
21
22 If the secret_counter is changed without secret_2fa change, counter
23 must only be increased. It must not go backwards (to avoid
24 replay attacks).
25 '''
26
27 if __debug__:
28 logger.debug("in auditor: user%s and newvalues: %r", nodeid, newvalues)
29
30 if ('secret_2fa' not in newvalues and
31 'secret_counter' not in newvalues):
32 # nothing to do
33 return
34
35 secret_counter = 0
36
37 if 'secret_2fa' in newvalues:
38
39 # verify that the secret is changed via the gen2fasecret
40 # action. This prevents the user from submitting a form
41 # that changes the secret to something simple like 32 A's.
42 #
43 # dbhandle has a creator property added by Generate2faSecret
44 # continue if:
45 # db has the Generate2faSecret property. Generate2faSecret action
46 # tx_Source is cli (command line)
47 # otherwise abort change.
48 if not (hasattr(db, 'Generate2faSecret') or (db.tx_Source in ['cli'])):
49 logger.error("Unauthorized path for two factor auth: "
50 "user id %s method %s" % (db.getuid(), db.tx_Source))
51 raise Reject("Found unauthorized path for changing "
52 "Two Factor Auth.")
53
54 secret_2fa = newvalues['secret_2fa']
55
56 logger.debug("processing secret_2fa change")
57 if 'secret_counter' not in newvalues:
58 # make sure it's not set to 0 or -2 in db
59 if db.user.get(nodeid, 'secret_counter') not in [0, -2]:
60 raise Reject("Two Factor Auth secret changed without "
61 "resetting counter.")
62 else:
63 secret_counter = newvalues['secret_counter']
64
65 if secret_2fa.password == '-':
66 if secret_counter != -1:
67 raise Reject("Disabling Two Factor Auth missing proper "
68 "counter value.")
69 else:
70 return
71
72 if re.match(r'^[A-Z2-7]{32}$', secret_2fa.password):
73 # -2 is TOTP sentinal
74 if secret_counter not in [0, -2]:
75 raise Reject("Resetting Two Factor Auth missing proper "
76 "counter value.")
77 else:
78 return
79 else:
80 raise Reject("Invalid secret value for Two Factor Auth")
81
82 # all cases where we are changing the secret_2fa are accounted for.
83 vals = {'new': newvalues['secret_counter'],
84 'old': db.user.get(nodeid, 'secret_counter')}
85
86 if vals['old'] >= 0:
87 if vals['new'] <= vals['old']:
88 # if it stays the same it hasn't changed so we won't be called.
89 raise Reject("Two Factor Auth secret count must not decrease: "
90 "new: %(new)s, old: %(old)s." % vals)
91 else:
92 raise Reject("Two Factor Auth is disabled. You must set "
93 "both secret and count at the same time "
94 "to enable it")
95
96
97 def init(db):
98 # fire before changes are made
99 db.user.audit('set', audit_2fa)
100 db.user.audit('create', audit_2fa)
101
102 # vim: sts=4 sw=4 et si
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.