Repository: privacyidea/privacyidea

Issue:
Enable deletion of validity period start/end date via UI
Currently the validity period start/end date can not be deleted via UI or API.
When editing the Token Info in the UI, the input fields can be emptied but this results in an internal server error (the empty string can not be parsed as a date).
We could allow sending an empty string to disable/delete the start/end date of the validity period.



Discussion:

Resolving patch:
diff --git a/privacyidea/lib/tokenclass.py b/privacyidea/lib/tokenclass.py
--- a/privacyidea/lib/tokenclass.py
+++ b/privacyidea/lib/tokenclass.py
@@ -77,6 +77,7 @@
 """
 import logging
 import hashlib
+import traceback
 from datetime import datetime, timedelta
 
 from .error import (TokenAdminError,
@@ -963,9 +964,16 @@ def set_validity_period_end(self, end_date):
                          throw an exception
         :type end_date: string
         """
-        #  upper layer will catch. we just try to verify the date format
-        d = parse_date_string(end_date)
-        self.add_tokeninfo("validity_period_end", d.strftime(DATE_FORMAT))
+        if not end_date:
+            self.del_tokeninfo("validity_period_end")
+        else:
+            #  upper layer will catch. we just try to verify the date format
+            try:
+                d = parse_date_string(end_date)
+            except ValueError as _e:
+                log.debug('{0!s}'.format(traceback.format_exc()))
+                raise TokenAdminError('Could not parse validity period end date!')
+            self.add_tokeninfo("validity_period_end", d.strftime(DATE_FORMAT))
 
     def get_validity_period_start(self):
         """
@@ -988,8 +996,16 @@ def set_validity_period_start(self, start_date):
                            throw an exception
         :type start_date: string
         """
-        d = parse_date_string(start_date)
-        self.add_tokeninfo("validity_period_start", d.strftime(DATE_FORMAT))
+        if not start_date:
+            self.del_tokeninfo("validity_period_start")
+        else:
+            try:
+                d = parse_date_string(start_date)
+            except ValueError as _e:
+                log.debug('{0!s}'.format(traceback.format_exc()))
+                raise TokenAdminError('Could not parse validity period start date!')
+
+            self.add_tokeninfo("validity_period_start", d.strftime(DATE_FORMAT))
 
     def set_next_pin_change(self, diff=None, password=False):
         """


Test patch:
diff --git a/tests/test_api_token.py b/tests/test_api_token.py
--- a/tests/test_api_token.py
+++ b/tests/test_api_token.py
@@ -841,6 +841,19 @@ def test_09_set_token_attributes(self):
             self.assertEqual(tokeninfo.get("validity_period_end"),
                              "2014-05-22T23:00+0200")
 
+        # check for broken validity dates
+        with self.app.test_request_context('/token/set/SET001',
+                                           method="POST",
+                                           data={"validity_period_start": "unknown"},
+                                           headers={'Authorization': self.at}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(res.status_code, 400, res)
+            result = res.json.get("result")
+            self.assertEqual(result['error']['code'], 301, result)
+            self.assertEqual(result['error']['message'],
+                             "ERR301: Could not parse validity period start date!",
+                             result)
+
     def test_10_set_token_realms(self):
         self._create_temp_token("REALM001")
 
diff --git a/tests/test_lib_tokenclass.py b/tests/test_lib_tokenclass.py
--- a/tests/test_lib_tokenclass.py
+++ b/tests/test_lib_tokenclass.py
@@ -3,7 +3,6 @@
 
 The lib.tokenclass depends on the DB model and lib.user
 """
-
 from .base import MyTestCase, FakeFlaskG
 from privacyidea.lib.resolver import (save_resolver, delete_resolver)
 from privacyidea.lib.realm import (set_realm, delete_realm)
@@ -14,6 +13,7 @@
                                     delete_privacyidea_config)
 from privacyidea.lib.crypto import geturandom
 from privacyidea.lib.utils import hexlify_and_unicode, to_unicode
+from privacyidea.lib.error import TokenAdminError
 from privacyidea.models import (Token,
                                 Config,
                                 Challenge)
@@ -306,18 +306,27 @@ def test_11_tokeninfo(self):
         token.set_validity_period_end("2014-12-30T16:00+0400")
         end = token.get_validity_period_end()
         self.assertTrue(end == "2014-12-30T16:00+0400", end)
-        self.assertRaises(Exception,
+        self.assertRaises(TokenAdminError,
                           token.set_validity_period_end, "wrong date")
         # handle validity start date
         token.set_validity_period_start("2014-12-30T16:00+0400")
         start = token.get_validity_period_start()
         self.assertTrue(start == "2014-12-30T16:00+0400", start)
-        self.assertRaises(Exception,
+        self.assertRaises(TokenAdminError,
                           token.set_validity_period_start, "wrong date")
         
         self.assertFalse(token.check_validity_period())
-        # THe token is valid till 2021, this should be enough!
-        token.set_validity_period_end("2021-12-30T16:00+0200")
+        # delete the validity period end by passing an empty string
+        token.set_validity_period_end('')
+        end = token.get_validity_period_end()
+        self.assertTrue(end == "", end)
+        self.assertTrue(token.check_validity_period())
+
+        # try the same for the validity period start
+        start_date_5d = datetime.datetime.now(tzlocal()) + datetime.timedelta(5)
+        token.set_validity_period_start(start_date_5d.strftime(DATE_FORMAT))
+        self.assertFalse(token.check_validity_period())
+        token.set_validity_period_start('')
         self.assertTrue(token.check_validity_period())
 
         token.set_validity_period_end("2015-05-22T22:00:00.000Z")