We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bad error handling in /ttype/<type> endpoint
We observed a bad error handling when requesting the `/ttype/<type>` endpoint.

In specific, we faced the following error:
```
[ERROR][privacyidea.app:1892] Exception on /ttype/push"}. [GET]
...
AttributeError: 'NoneType' object has no attribute 'api_endpoint'
```
Actually, we could fix the problem but it would be nice to fix this upstream right away.

### Top-level intent

Access the `/ttype/<type>` endpoint.

### Steps to reproduce

1. Query `/ttype/test` endpoint
2. There will be a NoneType error in the logs.

### Expected outcome

Proper error handling

### Actual outcome

NoneType exception.

### Configuration

* **privacyIDEA version**: v3.6.3
* **Installation method**: (from Ubuntu packages, github, PyPI, ...)
* **Python version**: 3
* **Operating system**: linux
* **Webserver**: apache


### Log file

**Set PI_LOGLEVEL = logging.DEBUG in pi.cfg and take a look at the privacyidea.log!**
**If appropriate, attach the log file or paste relevant portions.**


--- 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/api/ttype.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 # http://www.privacyidea.org
4 # (c) Cornelius Kölbel, privacyidea.org
5 #
6 # 2015-09-01 Cornelius Kölbel, <cornelius@privacyidea.org>
7 #            Initial writeup
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 This API endpoint is a generic endpoint that can be used by any token
24 type.
25 
26 The tokentype needs to implement a classmethod *api_endpoint* and can then be
27 called by /ttype/<tokentype>.
28 This way, each tokentype can create its own API without the need to change
29 the core API.
30 
31 The TiQR Token uses this API to implement its special functionalities. See
32 :ref:`code_tiqr_token`.
33 """
34 from flask import (Blueprint,
35                    request)
36 from .lib.utils import getParam
37 from ..lib.log import log_with
38 from flask import g, jsonify, current_app
39 import logging
40 from privacyidea.api.lib.utils import get_all_params
41 from privacyidea.lib.policy import PolicyClass
42 from privacyidea.lib.audit import getAudit
43 from privacyidea.lib.config import (get_token_class, get_from_config,
44                                     SYSCONF, ensure_no_config_object)
45 from privacyidea.lib.user import get_user_from_param
46 from privacyidea.lib.utils import get_client_ip
47 import json
48 
49 log = logging.getLogger(__name__)
50 
51 ttype_blueprint = Blueprint('ttype_blueprint', __name__)
52 
53 
54 @ttype_blueprint.before_request
55 def before_request():
56     """
57     This is executed before the request
58     """
59     ensure_no_config_object()
60     request.all_data = get_all_params(request)
61     privacyidea_server = current_app.config.get("PI_AUDIT_SERVERNAME") or \
62                          request.host
63     # Create a policy_object, that reads the database audit settings
64     # and contains the complete policy definition during the request.
65     # This audit_object can be used in the postpolicy and prepolicy and it
66     # can be passed to the innerpolicies.
67     g.policy_object = PolicyClass()
68     g.audit_object = getAudit(current_app.config)
69     # access_route contains the ip adresses of all clients, hops and proxies.
70     g.client_ip = get_client_ip(request,
71                                 get_from_config(SYSCONF.OVERRIDECLIENT))
72     g.serial = getParam(request.all_data, "serial") or None
73     g.audit_object.log({"success": False,
74                         "action_detail": "",
75                         "client": g.client_ip,
76                         "client_user_agent": request.user_agent.browser,
77                         "privacyidea_server": privacyidea_server,
78                         "action": "{0!s} {1!s}".format(request.method, request.url_rule),
79                         "info": ""})
80 
81 
82 @ttype_blueprint.route('/<ttype>', methods=['POST', 'GET'])
83 @log_with(log)
84 def token(ttype=None):
85     """
86     This is a special token function. Each token type can define an
87     additional API call, that does not need authentication on the REST API
88     level.
89 
90     :return: Token Type dependent
91     """
92     tokenc = get_token_class(ttype)
93     res = tokenc.api_endpoint(request, g)
94     serial = getParam(request.all_data, "serial")
95     user = get_user_from_param(request.all_data)
96     g.audit_object.log({"success": 1,
97                         "user": user.login,
98                         "realm": user.realm,
99                         "serial": serial,
100                         "token_type": ttype})
101     if res[0] == "json":
102         return jsonify(res[1])
103     elif res[0] in ["html", "plain"]:
104         return current_app.response_class(res[1], mimetype="text/{0!s}".format(res[0]))
105     elif len(res) == 2:
106         return current_app.response_class(json.dumps(res[1]),
107                                           mimetype="application/{0!s}".format(res[0]))
108     else:
109         return current_app.response_class(res[1], mimetype="application/octet-binary",
110                                           headers=res[2])
111
```
--- 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()

```

diff --git a/privacyidea/api/ttype.py b/privacyidea/api/ttype.py
--- a/privacyidea/api/ttype.py
+++ b/privacyidea/api/ttype.py
@@ -38,6 +38,7 @@
 from flask import g, jsonify, current_app
 import logging
 from privacyidea.api.lib.utils import get_all_params
+from privacyidea.lib.error import ParameterError
 from privacyidea.lib.policy import PolicyClass
 from privacyidea.lib.audit import getAudit
 from privacyidea.lib.config import (get_token_class, get_from_config,
@@ -90,6 +91,9 @@
     :return: Token Type dependent
     """
     tokenc = get_token_class(ttype)
+    if tokenc is None:
+        log.error(u"Invalid tokentype provided. ttype: {}".format(ttype.lower()))
+        raise ParameterError(u"Invalid tokentype provided. ttype: {}".format(ttype.lower()))
     res = tokenc.api_endpoint(request, g)
     serial = getParam(request.all_data, "serial")
     user = get_user_from_param(request.all_data)

{"golden_diff": "diff --git a/privacyidea/api/ttype.py b/privacyidea/api/ttype.py\n--- a/privacyidea/api/ttype.py\n+++ b/privacyidea/api/ttype.py\n@@ -38,6 +38,7 @@\n from flask import g, jsonify, current_app\n import logging\n from privacyidea.api.lib.utils import get_all_params\n+from privacyidea.lib.error import ParameterError\n from privacyidea.lib.policy import PolicyClass\n from privacyidea.lib.audit import getAudit\n from privacyidea.lib.config import (get_token_class, get_from_config,\n@@ -90,6 +91,9 @@\n     :return: Token Type dependent\n     \"\"\"\n     tokenc = get_token_class(ttype)\n+    if tokenc is None:\n+        log.error(u\"Invalid tokentype provided. ttype: {}\".format(ttype.lower()))\n+        raise ParameterError(u\"Invalid tokentype provided. ttype: {}\".format(ttype.lower()))\n     res = tokenc.api_endpoint(request, g)\n     serial = getParam(request.all_data, \"serial\")\n     user = get_user_from_param(request.all_data)\n", "issue": "Bad error handling in /ttype/<type> endpoint\nWe observed a bad error handling when requesting the `/ttype/<type>` endpoint.\r\n\r\nIn specific, we faced the following error:\r\n```\r\n[ERROR][privacyidea.app:1892] Exception on /ttype/push\"}. [GET]\r\n...\r\nAttributeError: 'NoneType' object has no attribute 'api_endpoint'\r\n```\r\nActually, we could fix the problem but it would be nice to fix this upstream right away.\r\n\r\n### Top-level intent\r\n\r\nAccess the `/ttype/<type>` endpoint.\r\n\r\n### Steps to reproduce\r\n\r\n1. Query `/ttype/test` endpoint\r\n2. There will be a NoneType error in the logs.\r\n\r\n### Expected outcome\r\n\r\nProper error handling\r\n\r\n### Actual outcome\r\n\r\nNoneType exception.\r\n\r\n### Configuration\r\n\r\n* **privacyIDEA version**: v3.6.3\r\n* **Installation method**: (from Ubuntu packages, github, PyPI, ...)\r\n* **Python version**: 3\r\n* **Operating system**: linux\r\n* **Webserver**: apache\r\n\r\n\r\n### Log file\r\n\r\n**Set PI_LOGLEVEL = logging.DEBUG in pi.cfg and take a look at the privacyidea.log!**\r\n**If appropriate, attach the log file or paste relevant portions.**\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# http://www.privacyidea.org\n# (c) Cornelius K\u00f6lbel, privacyidea.org\n#\n# 2015-09-01 Cornelius K\u00f6lbel, <cornelius@privacyidea.org>\n#            Initial writeup\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\"\"\"\nThis API endpoint is a generic endpoint that can be used by any token\ntype.\n\nThe tokentype needs to implement a classmethod *api_endpoint* and can then be\ncalled by /ttype/<tokentype>.\nThis way, each tokentype can create its own API without the need to change\nthe core API.\n\nThe TiQR Token uses this API to implement its special functionalities. See\n:ref:`code_tiqr_token`.\n\"\"\"\nfrom flask import (Blueprint,\n                   request)\nfrom .lib.utils import getParam\nfrom ..lib.log import log_with\nfrom flask import g, jsonify, current_app\nimport logging\nfrom privacyidea.api.lib.utils import get_all_params\nfrom privacyidea.lib.policy import PolicyClass\nfrom privacyidea.lib.audit import getAudit\nfrom privacyidea.lib.config import (get_token_class, get_from_config,\n                                    SYSCONF, ensure_no_config_object)\nfrom privacyidea.lib.user import get_user_from_param\nfrom privacyidea.lib.utils import get_client_ip\nimport json\n\nlog = logging.getLogger(__name__)\n\nttype_blueprint = Blueprint('ttype_blueprint', __name__)\n\n\n@ttype_blueprint.before_request\ndef before_request():\n    \"\"\"\n    This is executed before the request\n    \"\"\"\n    ensure_no_config_object()\n    request.all_data = get_all_params(request)\n    privacyidea_server = current_app.config.get(\"PI_AUDIT_SERVERNAME\") or \\\n                         request.host\n    # Create a policy_object, that reads the database audit settings\n    # and contains the complete policy definition during the request.\n    # This audit_object can be used in the postpolicy and prepolicy and it\n    # can be passed to the innerpolicies.\n    g.policy_object = PolicyClass()\n    g.audit_object = getAudit(current_app.config)\n    # access_route contains the ip adresses of all clients, hops and proxies.\n    g.client_ip = get_client_ip(request,\n                                get_from_config(SYSCONF.OVERRIDECLIENT))\n    g.serial = getParam(request.all_data, \"serial\") or None\n    g.audit_object.log({\"success\": False,\n                        \"action_detail\": \"\",\n                        \"client\": g.client_ip,\n                        \"client_user_agent\": request.user_agent.browser,\n                        \"privacyidea_server\": privacyidea_server,\n                        \"action\": \"{0!s} {1!s}\".format(request.method, request.url_rule),\n                        \"info\": \"\"})\n\n\n@ttype_blueprint.route('/<ttype>', methods=['POST', 'GET'])\n@log_with(log)\ndef token(ttype=None):\n    \"\"\"\n    This is a special token function. Each token type can define an\n    additional API call, that does not need authentication on the REST API\n    level.\n\n    :return: Token Type dependent\n    \"\"\"\n    tokenc = get_token_class(ttype)\n    res = tokenc.api_endpoint(request, g)\n    serial = getParam(request.all_data, \"serial\")\n    user = get_user_from_param(request.all_data)\n    g.audit_object.log({\"success\": 1,\n                        \"user\": user.login,\n                        \"realm\": user.realm,\n                        \"serial\": serial,\n                        \"token_type\": ttype})\n    if res[0] == \"json\":\n        return jsonify(res[1])\n    elif res[0] in [\"html\", \"plain\"]:\n        return current_app.response_class(res[1], mimetype=\"text/{0!s}\".format(res[0]))\n    elif len(res) == 2:\n        return current_app.response_class(json.dumps(res[1]),\n                                          mimetype=\"application/{0!s}\".format(res[0]))\n    else:\n        return current_app.response_class(res[1], mimetype=\"application/octet-binary\",\n                                          headers=res[2])\n", "path": "privacyidea/api/ttype.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n# http://www.privacyidea.org\n# (c) Cornelius K\u00f6lbel, privacyidea.org\n#\n# 2015-09-01 Cornelius K\u00f6lbel, <cornelius@privacyidea.org>\n#            Initial writeup\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\"\"\"\nThis API endpoint is a generic endpoint that can be used by any token\ntype.\n\nThe tokentype needs to implement a classmethod *api_endpoint* and can then be\ncalled by /ttype/<tokentype>.\nThis way, each tokentype can create its own API without the need to change\nthe core API.\n\nThe TiQR Token uses this API to implement its special functionalities. See\n:ref:`code_tiqr_token`.\n\"\"\"\nfrom flask import (Blueprint,\n                   request)\nfrom .lib.utils import getParam\nfrom ..lib.log import log_with\nfrom flask import g, jsonify, current_app\nimport logging\nfrom privacyidea.api.lib.utils import get_all_params\nfrom privacyidea.lib.error import ParameterError\nfrom privacyidea.lib.policy import PolicyClass\nfrom privacyidea.lib.audit import getAudit\nfrom privacyidea.lib.config import (get_token_class, get_from_config,\n                                    SYSCONF, ensure_no_config_object)\nfrom privacyidea.lib.user import get_user_from_param\nfrom privacyidea.lib.utils import get_client_ip\nimport json\n\nlog = logging.getLogger(__name__)\n\nttype_blueprint = Blueprint('ttype_blueprint', __name__)\n\n\n@ttype_blueprint.before_request\ndef before_request():\n    \"\"\"\n    This is executed before the request\n    \"\"\"\n    ensure_no_config_object()\n    request.all_data = get_all_params(request)\n    privacyidea_server = current_app.config.get(\"PI_AUDIT_SERVERNAME\") or \\\n                         request.host\n    # Create a policy_object, that reads the database audit settings\n    # and contains the complete policy definition during the request.\n    # This audit_object can be used in the postpolicy and prepolicy and it\n    # can be passed to the innerpolicies.\n    g.policy_object = PolicyClass()\n    g.audit_object = getAudit(current_app.config)\n    # access_route contains the ip adresses of all clients, hops and proxies.\n    g.client_ip = get_client_ip(request,\n                                get_from_config(SYSCONF.OVERRIDECLIENT))\n    g.serial = getParam(request.all_data, \"serial\") or None\n    g.audit_object.log({\"success\": False,\n                        \"action_detail\": \"\",\n                        \"client\": g.client_ip,\n                        \"client_user_agent\": request.user_agent.browser,\n                        \"privacyidea_server\": privacyidea_server,\n                        \"action\": \"{0!s} {1!s}\".format(request.method, request.url_rule),\n                        \"info\": \"\"})\n\n\n@ttype_blueprint.route('/<ttype>', methods=['POST', 'GET'])\n@log_with(log)\ndef token(ttype=None):\n    \"\"\"\n    This is a special token function. Each token type can define an\n    additional API call, that does not need authentication on the REST API\n    level.\n\n    :return: Token Type dependent\n    \"\"\"\n    tokenc = get_token_class(ttype)\n    if tokenc is None:\n        log.error(u\"Invalid tokentype provided. ttype: {}\".format(ttype.lower()))\n        raise ParameterError(u\"Invalid tokentype provided. ttype: {}\".format(ttype.lower()))\n    res = tokenc.api_endpoint(request, g)\n    serial = getParam(request.all_data, \"serial\")\n    user = get_user_from_param(request.all_data)\n    g.audit_object.log({\"success\": 1,\n                        \"user\": user.login,\n                        \"realm\": user.realm,\n                        \"serial\": serial,\n                        \"token_type\": ttype})\n    if res[0] == \"json\":\n        return jsonify(res[1])\n    elif res[0] in [\"html\", \"plain\"]:\n        return current_app.response_class(res[1], mimetype=\"text/{0!s}\".format(res[0]))\n    elif len(res) == 2:\n        return current_app.response_class(json.dumps(res[1]),\n                                          mimetype=\"application/{0!s}\".format(res[0]))\n    else:\n        return current_app.response_class(res[1], mimetype=\"application/octet-binary\",\n                                          headers=res[2])\n", "path": "privacyidea/api/ttype.py"}]}