Skip to content
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

Increase failcounter in case of wrong response to challenge #1546

Merged
merged 1 commit into from
Apr 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions privacyidea/lib/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -2200,10 +2200,12 @@ def check_token_list(tokenobject_list, passw, user=None, options=None, allow_res
elif challenge_response_token_list:
# The RESPONSE for a previous request of a challenge response token was
# found.
matching_challenge = False
for tokenobject in challenge_response_token_list:
if tokenobject.check_challenge_response(passw=passw,
options=options) >= 0:
reply_dict["serial"] = tokenobject.token.serial
matching_challenge = True
if tokenobject.is_active():
# OTP matches
res = True
Expand All @@ -2227,6 +2229,19 @@ def check_token_list(tokenobject_list, passw, user=None, options=None, allow_res
reply_dict["message"] = "Challenge matches, but token is inactive."
log.info("Received a valid response to a "
"challenge for inactive token {0!s}".format(tokenobject.token.serial))
if not res:
# We did not find any successful response, so we need to increase the
# failcounters
for token_obj in challenge_response_token_list:
token_obj.inc_failcount()
if not matching_challenge:
if len(challenge_response_token_list) == 1:
reply_dict["serial"] = challenge_response_token_list[0].token.serial
reply_dict["type"] = challenge_response_token_list[0].token.tokentype
reply_dict["message"] = "Response did not match the challenge."
else:
reply_dict["message"] = "Response did not match for " \
"{0!s} tokens.".format(len(challenge_response_token_list))

elif challenge_request_token_list:
# This is the initial REQUEST of a challenge response token
Expand Down
78 changes: 77 additions & 1 deletion tests/test_api_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
delete_privacyidea_config)
from privacyidea.lib.token import (get_tokens, init_token, remove_token,
reset_token, enable_token, revoke_token,
set_pin)
set_pin, get_one_token)
from privacyidea.lib.policy import SCOPE, ACTION, set_policy, delete_policy
from privacyidea.lib.event import set_event
from privacyidea.lib.event import delete_event
Expand Down Expand Up @@ -2936,6 +2936,82 @@ def test_08_challenge_text(self):
delete_policy("pol_footer")
delete_policy("pol_hotp")

def test_09_challenge_response_inc_failcounter(self):
# make HOTP a challenge response token
set_policy(name="pol_hotp",
scope=SCOPE.AUTH,
action="{0!s}=hotp".format(ACTION.CHALLENGERESPONSE))
init_token({"serial": "tok1",
"otpkey": self.otpkey,
"pin": "pin"}, user=User("cornelius", self.realm1))

# On token fails to challenge response
with self.app.test_request_context('/validate/check',
method='POST',
data={"user": "cornelius",
"pass": "pin"}):
res = self.app.full_dispatch_request()
self.assertTrue(res.status_code == 200, res)
data = json.loads(res.data)
self.assertTrue(data.get("result").get("status"))
self.assertFalse(data.get("result").get("value"))
detail = data.get("detail")
transaction_id = detail.get("transaction_id")

with self.app.test_request_context('/validate/check',
method='POST',
data={"user": "cornelius",
"pass": "wrongOTP",
"transaction_id": transaction_id}):
res = self.app.full_dispatch_request()
self.assertTrue(res.status_code == 200, res)
data = json.loads(res.data)
self.assertTrue(data.get("result").get("status"))
self.assertFalse(data.get("result").get("value"))
detail = data.get("detail")
self.assertEqual("tok1", detail.get("serial"))
self.assertEqual("hotp", detail.get("type"))
self.assertEqual("Response did not match the challenge.", detail.get("message"))

init_token({"serial": "tok2",
"otpkey": self.otpkey,
"pin": "pin"}, user=User("cornelius", self.realm1))

# Now, two tokens will not match
with self.app.test_request_context('/validate/check',
method='POST',
data={"user": "cornelius",
"pass": "pin"}):
res = self.app.full_dispatch_request()
self.assertTrue(res.status_code == 200, res)
data = json.loads(res.data)
self.assertTrue(data.get("result").get("status"))
self.assertFalse(data.get("result").get("value"))
detail = data.get("detail")
transaction_id = detail.get("transaction_id")

with self.app.test_request_context('/validate/check',
method='POST',
data={"user": "cornelius",
"pass": "wrongOTP",
"transaction_id": transaction_id}):
res = self.app.full_dispatch_request()
self.assertTrue(res.status_code == 200, res)
data = json.loads(res.data)
self.assertTrue(data.get("result").get("status"))
self.assertFalse(data.get("result").get("value"))
detail = data.get("detail")
self.assertTrue("serial" not in detail)
self.assertEqual("Response did not match for 2 tokens.", detail.get("message"))

# Now check the fail counters of the tokens
self.assertEqual(2, get_one_token(serial="tok1").token.failcount)
self.assertEqual(1, get_one_token(serial="tok2").token.failcount)

remove_token("tok1")
remove_token("tok2")
delete_policy("pol_hotp")


class TriggeredPoliciesTestCase(MyApiTestCase):

Expand Down