diff --git a/dal/settings.py b/dal/settings.py index 15e08b9..414c1d5 100644 --- a/dal/settings.py +++ b/dal/settings.py @@ -200,6 +200,7 @@ OTP_ADMIN_NAME = config('ADMIN_NAME') OTP_ADMIN_REALM = config('ADMIN_REALM') OTP_USER_REALM = config('USER_REALM') OTP_ALLOWED_REALMS = config('ALLOWED_REALMS', cast=Csv()) +OTP_TIMEOUT = config('OTP_TIMEOUT', default=10, cast=int) # seconds REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( diff --git a/dal/tests/test_flows.py b/dal/tests/test_flows.py index 36e8f72..a06bade 100644 --- a/dal/tests/test_flows.py +++ b/dal/tests/test_flows.py @@ -432,6 +432,17 @@ class OtpSeedTests(LdapTestCase): self.assertNotIn("cust-pass-123", out.getvalue()) self.assertNotIn("cust-pass-123", "\n".join(logs.output)) + def test_otp_requests_have_a_timeout(self): + uid = self.make_ldap_user(password="cust-pass-123") + self.login(uid, "cust-pass-123") + self.client.get("/seeds/") + self.client.post("/otp/", {"username": uid, "password": "cust-pass-123", + "realm": "ungleich-auth"}) + calls = self.otp_get.call_args_list + self.otp_post.call_args_list + self.assertTrue(calls) + for call in calls: + self.assertEqual(call.kwargs.get("timeout"), 10) + def test_api_rejects_bad_credentials(self): uid = self.make_ldap_user(password="cust-pass-123") resp = self.client.post("/otp/", {"username": uid, "password": "x", "realm": "ungleich-auth"}) diff --git a/dal/views.py b/dal/views.py index 5ae1cf3..089bd55 100644 --- a/dal/views.py +++ b/dal/views.py @@ -546,7 +546,8 @@ class ActivateAccount(View): 'auth_realm': settings.OTP_ADMIN_REALM, 'name': pending.username, 'realm': settings.OTP_USER_REALM - }), headers={'Content-Type': 'application/json'}) + }), headers={'Content-Type': 'application/json'}, + timeout=settings.OTP_TIMEOUT) if req.status_code != 201: logger.error("User {} failed to create its otp seed".format(pending.username)) @@ -633,7 +634,8 @@ class SeedRetrieveCreate(APIView): { 'auth_token': TOTP(settings.OTP_ADMIN_SEED).now(), 'auth_name': settings.OTP_ADMIN_NAME, - 'auth_realm': settings.OTP_ADMIN_REALM}), headers={'Content-Type': 'application/json'}) + 'auth_realm': settings.OTP_ADMIN_REALM}), headers={'Content-Type': 'application/json'}, + timeout=settings.OTP_TIMEOUT) response_data = json.loads(req.text) @@ -652,7 +654,8 @@ class SeedRetrieveCreate(APIView): 'auth_realm': settings.OTP_ADMIN_REALM, 'name': username, 'realm': realm - }), headers={'Content-Type': 'application/json'}) + }), headers={'Content-Type': 'application/json'}, + timeout=settings.OTP_TIMEOUT) if req.status_code == 201: msg = json.loads(req.text) return Response(msg, 201) @@ -669,6 +672,7 @@ class Seeds(LoginRequiredMixin, View): seedlist = [] response = requests.get( settings.OTP_SERVER, + timeout=settings.OTP_TIMEOUT, headers={'Content-Type': 'application/json'}, data=json.dumps( {'auth_name': settings.OTP_ADMIN_NAME, 'auth_realm': settings.OTP_ADMIN_REALM, 'auth_token': TOTP(settings.OTP_ADMIN_SEED).now()})) diff --git a/env.sample b/env.sample index 4b85778..f3cb002 100644 --- a/env.sample +++ b/env.sample @@ -46,3 +46,4 @@ ADMIN_REALM="" ADMIN_SEED="" # [secret] base32 TOTP seed of ADMIN_NAME USER_REALM="" # realm for seeds of new users ALLOWED_REALMS="" # comma separated realms users may request +#OTP_TIMEOUT=10 # seconds per request to the OTP server