Repository: privacyidea/privacyidea

Issue:
Auth Cache policy does not add entries to the authcache table
Currently the auth cache policy does not add entries to the auth cache, it just tries to validate given authentications.

Migrate EMail, TAN and VASCO token to Python 3
This slightly changes behavior: The VASCO token initialization now throws a ``ParameterError`` if the secret couldn't be decoded.
TOTP token uses wrong attribute for manual sync
The TOTP token uses the wrong attribute for calculating the resync (wrong attriubte: ``timeWindow``).
It should use the ``syncWindow``.
The ``_autosync`` method (actually does it right)[https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/tokens/totptoken.py#L398]!

When the timeWindow is increased, a manual resync would work:

      update tokeninfo set Value="1800" where Key="timeWindow" and token_id=62;

This is the problematic code:
https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/tokens/totptoken.py#L475

We need to use ``self.get_sync_window()`` instead of ``self.timeWindow`` for both ``res1`` and ``res2``.

As we would use the default syncwindow of 1000 just like with the autoresync this would be 1000 count, i.e. 1000*30sec = 500 Minutes --- roughly 11 hours, which is fine.

~~~~python
        # check 2nd value
        hmac2Otp = HmacOtp(secretHOtp,
                           counter,
                           otplen,
                           self.get_hashlib(self.hashlib))
        log.debug("{0!s} in otpkey: {1!s} ".format(otp2, secretHOtp))
        res2 = hmac2Otp.checkOtp(otp2,
                                 int(self.get_sync_window()),
                                 symetric=True)  # TEST -remove the 10
        log.debug("res 2: {0!r}".format(res2))
        # check 1st value
        hmac2Otp = HmacOtp(secretHOtp,
                           counter - 1,
                           otplen,
                           self.get_hashlib(self.hashlib))
        log.debug("{0!s} in otpkey: {1!s} ".format(otp1, secretHOtp))
        res1 = hmac2Otp.checkOtp(otp1,
                                 int(self.get_sync_window()),
                                 symetric=True)  # TEST -remove the 10
~~~~

We also need to add some sensible tests!


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/authcache.py b/privacyidea/lib/authcache.py
--- a/privacyidea/lib/authcache.py
+++ b/privacyidea/lib/authcache.py
@@ -19,7 +19,6 @@
 # You should have received a copy of the GNU Affero General Public
 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
-
 from ..models import AuthCache, db
 from sqlalchemy import and_
 from privacyidea.lib.crypto import hash
@@ -61,6 +60,21 @@ def delete_from_cache(username, realm, resolver, password):
     return r
 
 
+def cleanup(minutes):
+    """
+    Will delete all authcache entries, where last_auth column is older than
+    the given minutes.
+
+    :param minutes: Age of the last_authentication in minutes
+    :type minutes: int
+    :return:
+    """
+    cleanuptime = datetime.datetime.utcnow() - datetime.timedelta(minutes=minutes)
+    r = db.session.query(AuthCache).filter(AuthCache.last_auth < cleanuptime).delete()
+    db.session.commit()
+    return r
+
+
 def verify_in_cache(username, realm, resolver, password,
                     first_auth = None,
                     last_auth = None):
diff --git a/privacyidea/lib/policydecorators.py b/privacyidea/lib/policydecorators.py
--- a/privacyidea/lib/policydecorators.py
+++ b/privacyidea/lib/policydecorators.py
@@ -418,7 +418,6 @@ def auth_user_timelimit(wrapped_function, user_object, passw, options=None):
                     reply_dict["message"] = ("Only %s successfull "
                                              "authentications per %s"
                                              % (policy_count, tdelta))
-                    g.audit_object.add_policy(next(iter(max_success_dict.values())))
 
     return res, reply_dict
 
@@ -723,4 +722,4 @@ def reset_all_user_tokens(wrapped_function, *args, **kwds):
                         "registration token does not exist anymore and "
                         "cannot be reset.")
 
-    return r
\ No newline at end of file
+    return r
diff --git a/privacyidea/lib/tokens/totptoken.py b/privacyidea/lib/tokens/totptoken.py
--- a/privacyidea/lib/tokens/totptoken.py
+++ b/privacyidea/lib/tokens/totptoken.py
@@ -465,7 +465,8 @@ def resync(self, otp1, otp2, options=None):
         oCount = self.get_otp_count()
 
         log.debug("tokenCounter: {0!r}".format(oCount))
-        log.debug("now checking window {0!s}, timeStepping {1!s}".format(self.timewindow, self.timestep))
+        sync_window = self.get_sync_window()
+        log.debug("now checking window {0!s}, timeStepping {1!s}".format(sync_window, self.timestep))
         # check 2nd value
         hmac2Otp = HmacOtp(secretHOtp,
                            counter,
@@ -473,7 +474,7 @@ def resync(self, otp1, otp2, options=None):
                            self.get_hashlib(self.hashlib))
         log.debug("{0!s} in otpkey: {1!s} ".format(otp2, secretHOtp))
         res2 = hmac2Otp.checkOtp(otp2,
-                                 int(self.timewindow / self.timestep),
+                                 int(sync_window),
                                  symetric=True)  # TEST -remove the 10
         log.debug("res 2: {0!r}".format(res2))
         # check 1st value
@@ -483,7 +484,7 @@ def resync(self, otp1, otp2, options=None):
                            self.get_hashlib(self.hashlib))
         log.debug("{0!s} in otpkey: {1!s} ".format(otp1, secretHOtp))
         res1 = hmac2Otp.checkOtp(otp1,
-                                 int(self.timewindow / self.timestep),
+                                 int(sync_window),
                                  symetric=True)  # TEST -remove the 10
         log.debug("res 1: {0!r}".format(res1))
 


Test patch:
diff --git a/tests/test_lib_authcache.py b/tests/test_lib_authcache.py
--- a/tests/test_lib_authcache.py
+++ b/tests/test_lib_authcache.py
@@ -7,7 +7,8 @@
 
 from privacyidea.lib.authcache import (add_to_cache, delete_from_cache,
                                        update_cache_last_auth, verify_in_cache,
-                                       _hash_password)
+                                       _hash_password,
+                                       cleanup)
 from privacyidea.models import AuthCache
 import datetime
 
@@ -92,7 +93,23 @@ def test_03_delete_old_entries(self):
         r = AuthCache.query.filter(AuthCache.username == "grandpa").first()
         self.assertEqual(r, None)
 
+    def test_04_cleanup_authcache(self):
+        # cleanup everything!
+        r = cleanup(100000000)
+        # Create some entries:
+        AuthCache("grandpa", self.realm, self.resolver, _hash_password(self.password),
+                  first_auth=datetime.datetime.utcnow() - datetime.timedelta(
+                      days=10),
+                  last_auth=datetime.datetime.utcnow() - datetime.timedelta(
+                      days=2)).save()
+        AuthCache("grandpa", self.realm, self.resolver, _hash_password(self.password),
+                  first_auth=datetime.datetime.utcnow() - datetime.timedelta(
+                      minutes=10),
+                  last_auth=datetime.datetime.utcnow() - datetime.timedelta(
+                      minutes=2)).save()
 
-
-
+        # Now we delete entries, that are older than 20 minutes. Only the 2 days old
+        # should be deleted. Not the 2 minutes old.
+        r = cleanup(10)
+        self.assertEqual(1, r)
 
diff --git a/tests/test_lib_tokens_totp.py b/tests/test_lib_tokens_totp.py
--- a/tests/test_lib_tokens_totp.py
+++ b/tests/test_lib_tokens_totp.py
@@ -603,15 +603,24 @@ def test_23_resync(self):
         token = TotpTokenClass(db_token)
         token.update({"otpkey": self.otpkey,
                       "otplen": 6})
-        token.token.count = 47251400
         token.set_sync_window(1000)
-        # Successful resync
         # 705493 -> 47251649
         # 589836 -> 47251650
         # So the token might be at time 47251650,
         # but the server time is 47251600
+
+        # The server time is 2000*30 seconds further, the resync will fail
+        # 47253650 - 47251650 = 2000 ticks away
+        token.token.count = 47251400
+        r = token.resync("705493", "589836",
+                         options={"initTime": 47253650 * 30})
+        self.assertFalse(r)
+        # Successful resync
+        token.token.count = 47251400
+        # The server time is 200*30 seconds further
+        # 47251850 - 47251650 = 200 ticks away
         r = token.resync("705493", "589836",
-                         options={"initTime": 47251650 * 30})
+                         options={"initTime": 47251850 * 30})
         self.assertTrue(r is True, r)
         # resync fails
         token.token.count = 0