New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
839/offline refill #908
839/offline refill #908
Conversation
Codecov Report
@@ Coverage Diff @@
## master #908 +/- ##
==========================================
+ Coverage 95.38% 95.78% +0.39%
==========================================
Files 129 129
Lines 15913 17490 +1577
==========================================
+ Hits 15179 16752 +1573
- Misses 734 738 +4
Continue to review full report at Codecov.
|
privacyidea/api/validate.py
Outdated
:param pass: the last password (maybe password+OTP) entered by the user | ||
:return: | ||
""" | ||
refilltoken = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line can be deleted, as refilltoken
is defined later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct.
rounds = int(options.get("rounds", ROUNDS)) | ||
new_refilltoken = geturandom(REFILLTOKEN_LENGTH, hex=True) | ||
toks = get_tokens(serial=serial) | ||
if len(toks) == 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should add a log message in case the serial has no matching token? Because currently, in this case, a new refilltoken and an empty list are returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean something in case len(toks) != 1
?
Sounds sensible to me. But if we are the the refill do we not always have one token with this very serial number?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah, looking at it again, the current behavior seems okay at this point :)
privacyidea/api/validate.py
Outdated
tokenobj = get_tokens(serial=serial)[0] | ||
if tokenobj.get_tokeninfo("refilltoken") == refilltoken: | ||
# refill | ||
refilltoken, otps = MachineApplication.get_refill(serial, password, mdef.get("options")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading the code, I think if the given refilltoken is wrong, we simply return it and an empty list of OTPs. Maybe we should instead return an error message?
privacyidea/api/validate.py
Outdated
""" | ||
refilltoken = None | ||
otps = {} | ||
serial = getParam(request.all_data, "serial", required) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case the serial has no matching token, the following response is generated:
{
"detail": null,
"id": 1,
"jsonrpc": "2.0",
"result": {
"error": {
"code": -500,
"message": "'NoneType' object has no attribute 'machine_list'"
},
"status": false
},
...
}
This error is raised:
Traceback (most recent call last):
File "/home/fred/privacyidea/privacyidea/venv/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/home/fred/privacyidea/privacyidea/venv/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/fred/privacyidea/privacyidea/venv/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/fred/privacyidea/privacyidea/venv/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/home/fred/privacyidea/privacyidea/venv/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/fred/privacyidea/privacyidea/privacyidea/lib/event.py", line 60, in event_wrapper
f_result = func(*args, **kwds)
File "/home/fred/privacyidea/privacyidea/privacyidea/api/validate.py", line 184, in offlinerefill
machine_defs = list_token_machines(serial)
File "/home/fred/privacyidea/privacyidea/privacyidea/lib/log.py", line 154, in log_wrapper
return func(*args, **kwds)
File "/home/fred/privacyidea/privacyidea/privacyidea/lib/machine.py", line 332, in list_token_machines
for machine in db_token.machine_list:
AttributeError: 'NoneType' object has no attribute 'machine_list'
Maybe we should return a descriptive error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still a problem or is this outdated due to your rewrite?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot about this :) ce085ac now deals with error cases.
first_old_counter = 0 | ||
if otpval: | ||
# find the value in the old OTP values! This resets the token.count! | ||
matching_count = token_obj.check_otp(otpval, first_old_counter, count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think in case the passed OTP value is not found, matching_count
is set to -1, which results in a negative counter_diff
later on, which results in a decreasing OTP counter?
I tried to make the code a bit clearer in 839/offline-refill...839/offline-refill-wip.
|
This looks good to me.
|
raise ParameterError("Could not split password") | ||
current_token_counter = token_obj.token.count | ||
first_old_counter = current_token_counter - count | ||
if first_old_counter < 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cornelinux I'm wondering if this is even a legal case? In case of a refill, shouldn't the current token counter value always be higher than the refill amount?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. But I am not sure here. Because in some tests I ran into this problem.
if first_old_counter < 0:
log.error("This should not happen")
;-)
And I think you said: If we have a negative value, the counter on the server would be decreased. So this might be the safe net for states, that should not occur!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I guess this could happen if a token gets 100 offline OTPs (i.e. the online token counter is 101), then the administrator increases the offline OTP count to 200 and the client attempts a refill (i.e. 101 - 200 = -99
).
But apart from that, we ensure that we never decrease the counter on the server by checking for amount < 0
in get_offline_otps
.
We might have had an off-by-one error in |
@fredreichbier: I am merging this now! Then we adapt the pam module and check it in real live. |
Closes #839
refilltoken
is defined later on.len(toks) != 1
?Sounds sensible to me.
95.34% <ø> (ø)
100% <100%> (ø)
100% <100%> (ø)
100% <100%> (ø)
95.79% <0%> (+1.68%)
98.66% <0%> (+2.11%)
Continue to review full report at Codecov.
There, we have two methods for the two cases:
get_offline_otps
: just retrieve a number of offline OTPs (= hash(PIN + OTP))get_appropriate_refill
: given a PIN+OTP, calculate the size of the refill and callget_offline_otps
to get the right amount of new offline OTPsBut I have two comments:
get_appropriate_refill
toget_refill
. I always hope that the return value of a function is in fact appropriate ;-)get_authentication_item
I think thepassword
will never be empty. Because in get its value from thepass
parameter from validate/check. (see https://github.com/privacyidea/privacyidea/blob/master/privacyidea/api/lib/postpolicy.py#L480) The API call will always have a pass - or not? Do we have a test case for the empty password?get_refill
until now:Assume a user rolls out a new token and attaches it to a machine. Then, the user uses the OTP value with
count = 0
to get 100 offline OTPs. Then, the user presses the button once (= OTP withcount = 1
) and uses the OTP to get a refill. Up until now, the refill response had 0 elements. But shouldn't it have 1 element, as the user has consumed one OTP value already?I've attempted to fix the code and tests accordingly and pushed 243533d. But we should probably double-check that this is now the intended behavior :)
Then we adapt the pam module and check it in real live.
matching_count
is set to -1, which results in a negativecounter_diff
later on, which results in a decreasing OTP counter?options = options or {}
if options is None: options = {}
approach becauseoptions = options or {}
allocates a new dictionary in caseoptions
is the empty dictionary. But I guess this is just a matter of style :)if first_old_counter < 0:
log.error("This should not happen")
;-)
And I think you said: If we have a negative value, the counter on the server would be decreased. So this might be the safe net for states, that should not occur!
101 - 200 = -99
).But apart from that, we ensure that we never decrease the counter on the server by checking for
amount < 0
inget_offline_otps
.