prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Add VASCO token type to web UI
  As we cannot generate new secrets by ourselves, we just add a possibility to enter a hex-encoded blob directly.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `privacyidea/lib/tokens/vascotoken.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 #
  3 #  License:  AGPLv3
  4 #  contact:  http://www.privacyidea.org
  5 #
  6 #  2018-01-15   Friedrich Weber <friedrich.weber@netknights.it>
  7 #               Initial version of the VASCO token
  8 #
  9 # This code is free software; you can redistribute it and/or
  10 # modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11 # License as published by the Free Software Foundation; either
  12 # version 3 of the License, or any later version.
  13 #
  14 # This code is distributed in the hope that it will be useful,
  15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 # GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18 #
  19 # You should have received a copy of the GNU Affero General Public
  20 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  21 #
  22 #
  23 import binascii
  24 
  25 __doc__ = """This is the implementation of the VASCO token"""
  26 
  27 import logging
  28 from privacyidea.api.lib.utils import getParam
  29 from privacyidea.lib.utils import is_true
  30 from privacyidea.lib.decorators import check_token_locked
  31 from privacyidea.lib.error import ParameterError
  32 from privacyidea.lib.log import log_with
  33 from privacyidea.lib.tokenclass import TokenClass
  34 from privacyidea.lib.tokens.vasco import vasco_otp_check
  35 from privacyidea.lib import _
  36 
  37 optional = True
  38 required = False
  39 
  40 log = logging.getLogger(__name__)
  41 
  42 
  43 class VascoTokenClass(TokenClass):
  44     """
  45     Token class for VASCO Digipass tokens. Relies on vendor-specific
  46     shared library, whose location needs to be set in the PI_VASCO_LIBRARY
  47     config option.
  48 
  49     VASCO Tokens can be read from a CSV file which is structured as follows::
  50 
  51         <serial1>,<hexlify(blob1)>,vasco
  52         <serial2>,<hexlify(blob2)>,vasco
  53         ...
  54 
  55     whereas blobX is the 248-byte blob holding the token information.
  56     Consequently, hexlify(blobX) is a 496-character hex string.
  57 
  58     The CSV file can be imported by using the "Import Tokens" feature of the Web UI,
  59     where "OATH CSV" needs to be chosen as the file type.
  60     """
  61 
  62     def __init__(self, db_token):
  63         """
  64         constructor - create a token class object with its db token binding
  65 
  66         :param aToken: the db bound token
  67         """
  68         TokenClass.__init__(self, db_token)
  69         self.set_type(u"vasco")
  70         self.hKeyRequired = True
  71 
  72     @staticmethod
  73     def get_class_type():
  74         """
  75         return the class type identifier
  76         """
  77         return "vasco"
  78 
  79     @staticmethod
  80     def get_class_prefix():
  81         """
  82         return the token type prefix
  83         """
  84         # TODO: Revisit token type?
  85         return "VASC"
  86 
  87     @staticmethod
  88     @log_with(log)
  89     def get_class_info(key=None, ret='all'):
  90         """
  91         :param key: subsection identifier
  92         :type key: string
  93         :param ret: default return value, if nothing is found
  94         :type ret: user defined
  95         :return: subsection if key exists or user defined
  96         :rtype: dict or string
  97         """
  98         res = {'type': 'vasco',
  99                'title': 'VASCO Token',
  100                'description': _('VASCO Token: Authentication using VASCO tokens'),
  101                'user': ["enroll"],
  102                'policy': {},
  103                }
  104 
  105         if key:
  106             ret = res.get(key, {})
  107         else:
  108             if ret == 'all':
  109                 ret = res
  110         return ret
  111 
  112     @log_with(log)
  113     def update(self, param, reset_failcount=True):
  114         """
  115         update - process initialization parameters
  116 
  117         :param param: dict of initialization parameters
  118         :type param: dict
  119 
  120         :return: nothing
  121         """
  122         if is_true(getParam(param, 'genkey', optional)):
  123             raise ParameterError("Generating OTP keys is not supported")
  124 
  125         upd_param = param.copy()
  126 
  127         # If the OTP key is given, it is given as a 496-character hex string which
  128         # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob),
  129         # we unhexlify the OTP key
  130         if 'otpkey' in param:
  131             if len(param['otpkey']) != 496:
  132                 raise ParameterError('Expected OTP key as 496-character hex string, but length is {!s}'.format(
  133                     len(param['otpkey'])
  134                 ))
  135             upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey'])
  136 
  137         TokenClass.update(self, upd_param, reset_failcount)
  138 
  139     @check_token_locked
  140     def check_otp(self, otpval, counter=None, window=None, options=None):
  141         secret = self.token.get_otpkey().getKey()
  142         result, new_secret = vasco_otp_check(secret, otpval)
  143         # By default, setting a new OTP key resets the failcounter. In case of the VASCO token,
  144         # this would mean that the failcounter is reset at every authentication attempt
  145         # (regardless of success or failure), which must be avoided.
  146         self.token.set_otpkey(new_secret, reset_failcount=False)
  147         self.save()
  148 
  149         if result == 0:
  150             # Successful authentication
  151             return 0
  152         else:
  153             if result == 1:
  154                 # wrong OTP value, no log message
  155                 pass
  156             elif result == 201:
  157                 log.warning("A previous OTP value was used again!")
  158             elif result == 202:
  159                 log.warning("Token-internal fail counter reached its maximum!")
  160             else:
  161                 log.warning("VASCO token failed to authenticate, result: {!r}".format(result))
  162             return -1
  163
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/privacyidea/lib/tokens/vascotoken.py b/privacyidea/lib/tokens/vascotoken.py
  --- a/privacyidea/lib/tokens/vascotoken.py
  +++ b/privacyidea/lib/tokens/vascotoken.py
  @@ -98,7 +98,10 @@
           res = {'type': 'vasco',
                  'title': 'VASCO Token',
                  'description': _('VASCO Token: Authentication using VASCO tokens'),
  -               'user': ["enroll"],
  +               # If this was set, the user could enroll a Vasco token via the API
  +               #'user': ["enroll"],
  +               # only administrators can enroll the token in the UI
  +               'ui_enroll': ["admin"],
                  'policy': {},
                  }
  
verification_info: {"golden_diff": "diff --git a/privacyidea/lib/tokens/vascotoken.py b/privacyidea/lib/tokens/vascotoken.py\n--- a/privacyidea/lib/tokens/vascotoken.py\n+++ b/privacyidea/lib/tokens/vascotoken.py\n@@ -98,7 +98,10 @@\n         res = {'type': 'vasco',\n                'title': 'VASCO Token',\n                'description': _('VASCO Token: Authentication using VASCO tokens'),\n-               'user': [\"enroll\"],\n+               # If this was set, the user could enroll a Vasco token via the API\n+               #'user': [\"enroll\"],\n+               # only administrators can enroll the token in the UI\n+               'ui_enroll': [\"admin\"],\n                'policy': {},\n                }\n", "issue": "Add VASCO token type to web UI\nAs we cannot generate new secrets by ourselves, we just add a possibility to enter a hex-encoded blob directly.\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  License:  AGPLv3\n#  contact:  http://www.privacyidea.org\n#\n#  2018-01-15   Friedrich Weber <friedrich.weber@netknights.it>\n#               Initial version of the VASCO token\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n#\nimport binascii\n\n__doc__ = \"\"\"This is the implementation of the VASCO token\"\"\"\n\nimport logging\nfrom privacyidea.api.lib.utils import getParam\nfrom privacyidea.lib.utils import is_true\nfrom privacyidea.lib.decorators import check_token_locked\nfrom privacyidea.lib.error import ParameterError\nfrom privacyidea.lib.log import log_with\nfrom privacyidea.lib.tokenclass import TokenClass\nfrom privacyidea.lib.tokens.vasco import vasco_otp_check\nfrom privacyidea.lib import _\n\noptional = True\nrequired = False\n\nlog = logging.getLogger(__name__)\n\n\nclass VascoTokenClass(TokenClass):\n    \"\"\"\n    Token class for VASCO Digipass tokens. Relies on vendor-specific\n    shared library, whose location needs to be set in the PI_VASCO_LIBRARY\n    config option.\n\n    VASCO Tokens can be read from a CSV file which is structured as follows::\n\n        <serial1>,<hexlify(blob1)>,vasco\n        <serial2>,<hexlify(blob2)>,vasco\n        ...\n\n    whereas blobX is the 248-byte blob holding the token information.\n    Consequently, hexlify(blobX) is a 496-character hex string.\n\n    The CSV file can be imported by using the \"Import Tokens\" feature of the Web UI,\n    where \"OATH CSV\" needs to be chosen as the file type.\n    \"\"\"\n\n    def __init__(self, db_token):\n        \"\"\"\n        constructor - create a token class object with its db token binding\n\n        :param aToken: the db bound token\n        \"\"\"\n        TokenClass.__init__(self, db_token)\n        self.set_type(u\"vasco\")\n        self.hKeyRequired = True\n\n    @staticmethod\n    def get_class_type():\n        \"\"\"\n        return the class type identifier\n        \"\"\"\n        return \"vasco\"\n\n    @staticmethod\n    def get_class_prefix():\n        \"\"\"\n        return the token type prefix\n        \"\"\"\n        # TODO: Revisit token type?\n        return \"VASC\"\n\n    @staticmethod\n    @log_with(log)\n    def get_class_info(key=None, ret='all'):\n        \"\"\"\n        :param key: subsection identifier\n        :type key: string\n        :param ret: default return value, if nothing is found\n        :type ret: user defined\n        :return: subsection if key exists or user defined\n        :rtype: dict or string\n        \"\"\"\n        res = {'type': 'vasco',\n               'title': 'VASCO Token',\n               'description': _('VASCO Token: Authentication using VASCO tokens'),\n               'user': [\"enroll\"],\n               'policy': {},\n               }\n\n        if key:\n            ret = res.get(key, {})\n        else:\n            if ret == 'all':\n                ret = res\n        return ret\n\n    @log_with(log)\n    def update(self, param, reset_failcount=True):\n        \"\"\"\n        update - process initialization parameters\n\n        :param param: dict of initialization parameters\n        :type param: dict\n\n        :return: nothing\n        \"\"\"\n        if is_true(getParam(param, 'genkey', optional)):\n            raise ParameterError(\"Generating OTP keys is not supported\")\n\n        upd_param = param.copy()\n\n        # If the OTP key is given, it is given as a 496-character hex string which\n        # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob),\n        # we unhexlify the OTP key\n        if 'otpkey' in param:\n            if len(param['otpkey']) != 496:\n                raise ParameterError('Expected OTP key as 496-character hex string, but length is {!s}'.format(\n                    len(param['otpkey'])\n                ))\n            upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey'])\n\n        TokenClass.update(self, upd_param, reset_failcount)\n\n    @check_token_locked\n    def check_otp(self, otpval, counter=None, window=None, options=None):\n        secret = self.token.get_otpkey().getKey()\n        result, new_secret = vasco_otp_check(secret, otpval)\n        # By default, setting a new OTP key resets the failcounter. In case of the VASCO token,\n        # this would mean that the failcounter is reset at every authentication attempt\n        # (regardless of success or failure), which must be avoided.\n        self.token.set_otpkey(new_secret, reset_failcount=False)\n        self.save()\n\n        if result == 0:\n            # Successful authentication\n            return 0\n        else:\n            if result == 1:\n                # wrong OTP value, no log message\n                pass\n            elif result == 201:\n                log.warning(\"A previous OTP value was used again!\")\n            elif result == 202:\n                log.warning(\"Token-internal fail counter reached its maximum!\")\n            else:\n                log.warning(\"VASCO token failed to authenticate, result: {!r}\".format(result))\n            return -1\n", "path": "privacyidea/lib/tokens/vascotoken.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  License:  AGPLv3\n#  contact:  http://www.privacyidea.org\n#\n#  2018-01-15   Friedrich Weber <friedrich.weber@netknights.it>\n#               Initial version of the VASCO token\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n#\nimport binascii\n\n__doc__ = \"\"\"This is the implementation of the VASCO token\"\"\"\n\nimport logging\nfrom privacyidea.api.lib.utils import getParam\nfrom privacyidea.lib.utils import is_true\nfrom privacyidea.lib.decorators import check_token_locked\nfrom privacyidea.lib.error import ParameterError\nfrom privacyidea.lib.log import log_with\nfrom privacyidea.lib.tokenclass import TokenClass\nfrom privacyidea.lib.tokens.vasco import vasco_otp_check\nfrom privacyidea.lib import _\n\noptional = True\nrequired = False\n\nlog = logging.getLogger(__name__)\n\n\nclass VascoTokenClass(TokenClass):\n    \"\"\"\n    Token class for VASCO Digipass tokens. Relies on vendor-specific\n    shared library, whose location needs to be set in the PI_VASCO_LIBRARY\n    config option.\n\n    VASCO Tokens can be read from a CSV file which is structured as follows::\n\n        <serial1>,<hexlify(blob1)>,vasco\n        <serial2>,<hexlify(blob2)>,vasco\n        ...\n\n    whereas blobX is the 248-byte blob holding the token information.\n    Consequently, hexlify(blobX) is a 496-character hex string.\n\n    The CSV file can be imported by using the \"Import Tokens\" feature of the Web UI,\n    where \"OATH CSV\" needs to be chosen as the file type.\n    \"\"\"\n\n    def __init__(self, db_token):\n        \"\"\"\n        constructor - create a token class object with its db token binding\n\n        :param aToken: the db bound token\n        \"\"\"\n        TokenClass.__init__(self, db_token)\n        self.set_type(u\"vasco\")\n        self.hKeyRequired = True\n\n    @staticmethod\n    def get_class_type():\n        \"\"\"\n        return the class type identifier\n        \"\"\"\n        return \"vasco\"\n\n    @staticmethod\n    def get_class_prefix():\n        \"\"\"\n        return the token type prefix\n        \"\"\"\n        # TODO: Revisit token type?\n        return \"VASC\"\n\n    @staticmethod\n    @log_with(log)\n    def get_class_info(key=None, ret='all'):\n        \"\"\"\n        :param key: subsection identifier\n        :type key: string\n        :param ret: default return value, if nothing is found\n        :type ret: user defined\n        :return: subsection if key exists or user defined\n        :rtype: dict or string\n        \"\"\"\n        res = {'type': 'vasco',\n               'title': 'VASCO Token',\n               'description': _('VASCO Token: Authentication using VASCO tokens'),\n               # If this was set, the user could enroll a Vasco token via the API\n               #'user': [\"enroll\"],\n               # only administrators can enroll the token in the UI\n               'ui_enroll': [\"admin\"],\n               'policy': {},\n               }\n\n        if key:\n            ret = res.get(key, {})\n        else:\n            if ret == 'all':\n                ret = res\n        return ret\n\n    @log_with(log)\n    def update(self, param, reset_failcount=True):\n        \"\"\"\n        update - process initialization parameters\n\n        :param param: dict of initialization parameters\n        :type param: dict\n\n        :return: nothing\n        \"\"\"\n        if is_true(getParam(param, 'genkey', optional)):\n            raise ParameterError(\"Generating OTP keys is not supported\")\n\n        upd_param = param.copy()\n\n        # If the OTP key is given, it is given as a 496-character hex string which\n        # encodes a 248-byte blob. As we want to set a 248-byte OTPKey (= Blob),\n        # we unhexlify the OTP key\n        if 'otpkey' in param:\n            if len(param['otpkey']) != 496:\n                raise ParameterError('Expected OTP key as 496-character hex string, but length is {!s}'.format(\n                    len(param['otpkey'])\n                ))\n            upd_param['otpkey'] = binascii.unhexlify(upd_param['otpkey'])\n\n        TokenClass.update(self, upd_param, reset_failcount)\n\n    @check_token_locked\n    def check_otp(self, otpval, counter=None, window=None, options=None):\n        secret = self.token.get_otpkey().getKey()\n        result, new_secret = vasco_otp_check(secret, otpval)\n        # By default, setting a new OTP key resets the failcounter. In case of the VASCO token,\n        # this would mean that the failcounter is reset at every authentication attempt\n        # (regardless of success or failure), which must be avoided.\n        self.token.set_otpkey(new_secret, reset_failcount=False)\n        self.save()\n\n        if result == 0:\n            # Successful authentication\n            return 0\n        else:\n            if result == 1:\n                # wrong OTP value, no log message\n                pass\n            elif result == 201:\n                log.warning(\"A previous OTP value was used again!\")\n            elif result == 202:\n                log.warning(\"Token-internal fail counter reached its maximum!\")\n            else:\n                log.warning(\"VASCO token failed to authenticate, result: {!r}\".format(result))\n            return -1\n", "path": "privacyidea/lib/tokens/vascotoken.py"}]}