# Buggy code
def post(self):  # pylint: disable=arguments-differ
    """
        :ref:`Authenticate <rest_tornado-auth>` against Salt's eauth system

        .. http:post:: /login

            :reqheader X-Auth-Token: |req_token|
            :reqheader Accept: |req_accept|
            :reqheader Content-Type: |req_ct|

            :form eauth: the eauth backend configured for the user
            :form username: username
            :form password: password

            :status 200: |200|
            :status 400: |400|
            :status 401: |401|
            :status 406: |406|
            :status 500: |500|

        **Example request:**

        .. code-block:: bash

            curl -si localhost:8000/login \\
                    -H "Accept: application/json" \\
                    -d username='saltuser' \\
                    -d password='saltpass' \\
                    -d eauth='pam'

        .. code-block:: text

            POST / HTTP/1.1
            Host: localhost:8000
            Content-Length: 42
            Content-Type: application/x-www-form-urlencoded
            Accept: application/json

            username=saltuser&password=saltpass&eauth=pam

        **Example response:**

        .. code-block:: text

            HTTP/1.1 200 OK
            Content-Type: application/json
            Content-Length: 206
            X-Auth-Token: 6d1b722e
            Set-Cookie: session_id=6d1b722e; expires=Sat, 17 Nov 2012 03:23:52 GMT; Path=/

            {"return": {
                "token": "6d1b722e",
                "start": 1363805943.776223,
                "expire": 1363849143.776224,
                "user": "saltuser",
                "eauth": "pam",
                "perms": [
                    "grains.*",
                    "status.*",
                    "sys.*",
                    "test.*"
                ]
            }}
        """
    try:
        if not isinstance(self.request_payload, dict):
            self.send_error(400)
            return

        creds = {
            "username": self.request_payload["username"],
            "password": self.request_payload["password"],
            "eauth": self.request_payload["eauth"],
        }
    # if any of the args are missing, its a bad request
    except KeyError:
        self.send_error(400)
        return

    token = self.application.auth.mk_token(creds)
    if "token" not in token:
        # TODO: nicer error message
        # 'Could not authenticate using provided credentials')
        self.send_error(401)
        # return since we don't want to execute any more
        return
    self.set_cookie(AUTH_COOKIE_NAME, token["token"])

    # Grab eauth config for the current backend for the current user
    try:
        eauth = self.application.opts["external_auth"][token["eauth"]]
        # Get sum of '*' perms, user-specific perms, and group-specific perms
        perms = eauth.get(token["name"], [])
        perms.extend(eauth.get("*", []))

        if "groups" in token and token["groups"]:
            user_groups = set(token["groups"])
            eauth_groups = set([i.rstrip("%") for i in eauth.keys() if i.endswith("%")])

            for group in user_groups & eauth_groups:
                perms.extend(eauth["{0}%".format(group)])

        perms = sorted(list(set(perms)))
    # If we can't find the creds, then they aren't authorized
    except KeyError:
        self.send_error(401)
        return

    except (AttributeError, IndexError):
        log.debug(
            "Configuration for external_auth malformed for eauth '%s', and user '%s'.",
            token.get("eauth"),
            token.get("name"),
            exc_info=True,
        )
        # TODO better error -- 'Configuration for external_auth could not be read.'
        self.send_error(500)
        return

    ret = {
        "return": [
            {
                "token": token["token"],
                "expire": token["expire"],
                "start": token["start"],
                "user": token["name"],
                "eauth": token["eauth"],
                "perms": perms,
            }
        ]
    }

    self.write(self.serialize(ret))


# Traceback (TypeError)
2018-09-07 19:06:22,631 [tornado.application:1495][ERROR   ][998] Uncaught exception POST /run (127.0.0.1)
HTTPServerRequest(protocol='http', host='localhost:8000', method='POST', uri='/run', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'Host': 'localhost:8000', 'Content-Type': 'application/json', 'Content-Length': '125', 'Accept': 'application/x-yaml', 'User-Agent': 'curl/7.29.0'})
Traceback (most recent call last):
File "/usr/lib64/python2.7/site-packages/tornado/web.py", line 1368, in _stack_context_handle_exception
raise_exc_info((type, value, traceback))
File "/usr/lib64/python2.7/site-packages/tornado/stack_context.py", line 313, in wrapped
ret = fn(*args, **kwargs)
File "/usr/lib64/python2.7/site-packages/tornado/gen.py", line 770, in <lambda>
IOLoop.current().call_later(duration, lambda: f.set_result(None))
File "/usr/lib64/python2.7/site-packages/tornado/concurrent.py", line 253, in set_result
self._set_done()
File "/usr/lib64/python2.7/site-packages/tornado/concurrent.py", line 297, in _set_done
for cb in self._callbacks:
TypeError: 'NoneType' object is not iterable

# Fixed code
def post(self):  # pylint: disable=arguments-differ
    """
        :ref:`Authenticate <rest_tornado-auth>` against Salt's eauth system

        .. http:post:: /login

            :reqheader X-Auth-Token: |req_token|
            :reqheader Accept: |req_accept|
            :reqheader Content-Type: |req_ct|

            :form eauth: the eauth backend configured for the user
            :form username: username
            :form password: password

            :status 200: |200|
            :status 400: |400|
            :status 401: |401|
            :status 406: |406|
            :status 500: |500|

        **Example request:**

        .. code-block:: bash

            curl -si localhost:8000/login \\
                    -H "Accept: application/json" \\
                    -d username='saltuser' \\
                    -d password='saltpass' \\
                    -d eauth='pam'

        .. code-block:: text

            POST / HTTP/1.1
            Host: localhost:8000
            Content-Length: 42
            Content-Type: application/x-www-form-urlencoded
            Accept: application/json

            username=saltuser&password=saltpass&eauth=pam

        **Example response:**

        .. code-block:: text

            HTTP/1.1 200 OK
            Content-Type: application/json
            Content-Length: 206
            X-Auth-Token: 6d1b722e
            Set-Cookie: session_id=6d1b722e; expires=Sat, 17 Nov 2012 03:23:52 GMT; Path=/

            {"return": {
                "token": "6d1b722e",
                "start": 1363805943.776223,
                "expire": 1363849143.776224,
                "user": "saltuser",
                "eauth": "pam",
                "perms": [
                    "grains.*",
                    "status.*",
                    "sys.*",
                    "test.*"
                ]
            }}
        """
    try:
        if not isinstance(self.request_payload, dict):
            self.send_error(400)
            return

        creds = {
            "username": self.request_payload["username"],
            "password": self.request_payload["password"],
            "eauth": self.request_payload["eauth"],
        }
    # if any of the args are missing, its a bad request
    except KeyError:
        self.send_error(400)
        return

    token = self.application.auth.mk_token(creds)
    if "token" not in token:
        # TODO: nicer error message
        # 'Could not authenticate using provided credentials')
        self.send_error(401)
        # return since we don't want to execute any more
        return
    self.set_cookie(AUTH_COOKIE_NAME, token["token"])

    # Grab eauth config for the current backend for the current user
    try:
        eauth = self.application.opts["external_auth"][token["eauth"]]
        # Get sum of '*' perms, user-specific perms, and group-specific perms
        perms = eauth.get(token["name"], [])
        perms.extend(eauth.get("*", []))

        if "groups" in token and token["groups"]:
            user_groups = set(token["groups"])
            eauth_groups = {i.rstrip("%") for i in eauth.keys() if i.endswith("%")}

            for group in user_groups & eauth_groups:
                perms.extend(eauth["{}%".format(group)])

        perms = sorted(list(set(perms)))
    # If we can't find the creds, then they aren't authorized
    except KeyError:
        self.send_error(401)
        return

    except (AttributeError, IndexError):
        log.debug(
            "Configuration for external_auth malformed for eauth '%s', and user '%s'.",
            token.get("eauth"),
            token.get("name"),
            exc_info=True,
        )
        # TODO better error -- 'Configuration for external_auth could not be read.'
        self.send_error(500)
        return

    ret = {
        "return": [
            {
                "token": token["token"],
                "expire": token["expire"],
                "start": token["start"],
                "user": token["name"],
                "eauth": token["eauth"],
                "perms": perms,
            }
        ]
    }

    self.write(self.serialize(ret))