# Buggy code
async def on_POST(self, request):
    body = parse_json_object_from_request(request)

    client_addr = request.getClientIP()

    self.ratelimiter.ratelimit(client_addr, update=False)

    kind = b"user"
    if b"kind" in request.args:
        kind = request.args[b"kind"][0]

    if kind == b"guest":
        ret = await self._do_guest_registration(body, address=client_addr)
        return ret
    elif kind != b"user":
        raise UnrecognizedRequestError(
            "Do not understand membership kind: %s" % (kind.decode("utf8"),)
        )

    # Pull out the provided username and do basic sanity checks early since
    # the auth layer will store these in sessions.
    desired_username = None
    if "username" in body:
        if not isinstance(body["username"], str) or len(body["username"]) > 512:
            raise SynapseError(400, "Invalid username")
        desired_username = body["username"]

    appservice = None
    if self.auth.has_access_token(request):
        appservice = self.auth.get_appservice_by_req(request)

    # fork off as soon as possible for ASes which have completely
    # different registration flows to normal users

    # == Application Service Registration ==
    if appservice:
        # Set the desired user according to the AS API (which uses the
        # 'user' key not 'username'). Since this is a new addition, we'll
        # fallback to 'username' if they gave one.
        desired_username = body.get("user", desired_username)

        # XXX we should check that desired_username is valid. Currently
        # we give appservices carte blanche for any insanity in mxids,
        # because the IRC bridges rely on being able to register stupid
        # IDs.

        access_token = self.auth.get_access_token_from_request(request)

        if isinstance(desired_username, str):
            result = await self._do_appservice_registration(
                desired_username, access_token, body
            )
        return 200, result  # we throw for non 200 responses

    # == Normal User Registration == (everyone else)
    if not self._registration_enabled:
        raise SynapseError(403, "Registration has been disabled")

    # For regular registration, convert the provided username to lowercase
    # before attempting to register it. This should mean that people who try
    # to register with upper-case in their usernames don't get a nasty surprise.
    #
    # Note that we treat usernames case-insensitively in login, so they are
    # free to carry on imagining that their username is CrAzYh4cKeR if that
    # keeps them happy.
    if desired_username is not None:
        desired_username = desired_username.lower()

    # Check if this account is upgrading from a guest account.
    guest_access_token = body.get("guest_access_token", None)

    # Pull out the provided password and do basic sanity checks early.
    #
    # Note that we remove the password from the body since the auth layer
    # will store the body in the session and we don't want a plaintext
    # password store there.
    password = body.pop("password", None)
    if password is not None:
        if not isinstance(password, str) or len(password) > 512:
            raise SynapseError(400, "Invalid password")
        self.password_policy_handler.validate_password(password)

    if "initial_device_display_name" in body and password is None:
        # ignore 'initial_device_display_name' if sent without
        # a password to work around a client bug where it sent
        # the 'initial_device_display_name' param alone, wiping out
        # the original registration params
        logger.warning("Ignoring initial_device_display_name without password")
        del body["initial_device_display_name"]

    session_id = self.auth_handler.get_session_id(body)
    registered_user_id = None
    password_hash = None
    if session_id:
        # if we get a registered user id out of here, it means we previously
        # registered a user for this session, so we could just return the
        # user here. We carry on and go through the auth checks though,
        # for paranoia.
        registered_user_id = await self.auth_handler.get_session_data(
            session_id, "registered_user_id", None
        )
        # Extract the previously-hashed password from the session.
        password_hash = await self.auth_handler.get_session_data(
            session_id, "password_hash", None
        )

    # Ensure that the username is valid.
    if desired_username is not None:
        await self.registration_handler.check_username(
            desired_username,
            guest_access_token=guest_access_token,
            assigned_user_id=registered_user_id,
        )

    # Check if the user-interactive authentication flows are complete, if
    # not this will raise a user-interactive auth error.
    try:
        auth_result, params, session_id = await self.auth_handler.check_ui_auth(
            self._registration_flows,
            request,
            body,
            self.hs.get_ip_from_request(request),
            "register a new account",
        )
    except InteractiveAuthIncompleteError as e:
        # The user needs to provide more steps to complete auth.
        #
        # Hash the password and store it with the session since the client
        # is not required to provide the password again.
        #
        # If a password hash was previously stored we will not attempt to
        # re-hash and store it for efficiency. This assumes the password
        # does not change throughout the authentication flow, but this
        # should be fine since the data is meant to be consistent.
        if not password_hash and password:
            password_hash = await self.auth_handler.hash(password)
            await self.auth_handler.set_session_data(
                e.session_id, "password_hash", password_hash
            )
        raise

    # Check that we're not trying to register a denied 3pid.
    #
    # the user-facing checks will probably already have happened in
    # /register/email/requestToken when we requested a 3pid, but that's not
    # guaranteed.
    if auth_result:
        for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
            if login_type in auth_result:
                medium = auth_result[login_type]["medium"]
                address = auth_result[login_type]["address"]

                if not check_3pid_allowed(self.hs, medium, address):
                    raise SynapseError(
                        403,
                        "Third party identifiers (email/phone numbers)"
                        + " are not authorized on this server",
                        Codes.THREEPID_DENIED,
                    )

    if registered_user_id is not None:
        logger.info(
            "Already registered user ID %r for this session", registered_user_id
        )
        # don't re-register the threepids
        registered = False
    else:
        # If we have a password in this request, prefer it. Otherwise, there
        # might be a password hash from an earlier request.
        if password:
            password_hash = await self.auth_handler.hash(password)
        if not password_hash:
            raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)

        desired_username = params.get("username", None)
        guest_access_token = params.get("guest_access_token", None)

        if desired_username is not None:
            desired_username = desired_username.lower()

        threepid = None
        if auth_result:
            threepid = auth_result.get(LoginType.EMAIL_IDENTITY)

            # Also check that we're not trying to register a 3pid that's already
            # been registered.
            #
            # This has probably happened in /register/email/requestToken as well,
            # but if a user hits this endpoint twice then clicks on each link from
            # the two activation emails, they would register the same 3pid twice.
            for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
                if login_type in auth_result:
                    medium = auth_result[login_type]["medium"]
                    address = auth_result[login_type]["address"]
                    # For emails, canonicalise the address.
                    # We store all email addresses canonicalised in the DB.
                    # (See on_POST in EmailThreepidRequestTokenRestServlet
                    # in synapse/rest/client/v2_alpha/account.py)
                    if medium == "email":
                        try:
                            address = canonicalise_email(address)
                        except ValueError as e:
                            raise SynapseError(400, str(e))

                    existing_user_id = await self.store.get_user_id_by_threepid(
                        medium, address
                    )

                    if existing_user_id is not None:
                        raise SynapseError(
                            400,
                            "%s is already in use" % medium,
                            Codes.THREEPID_IN_USE,
                        )

        entries = await self.store.get_user_agents_ips_to_ui_auth_session(session_id)

        registered_user_id = await self.registration_handler.register_user(
            localpart=desired_username,
            password_hash=password_hash,
            guest_access_token=guest_access_token,
            threepid=threepid,
            address=client_addr,
            user_agent_ips=entries,
        )
        # Necessary due to auth checks prior to the threepid being
        # written to the db
        if threepid:
            if is_threepid_reserved(
                self.hs.config.mau_limits_reserved_threepids, threepid
            ):
                await self.store.upsert_monthly_active_user(registered_user_id)

        # Remember that the user account has been registered (and the user
        # ID it was registered with, since it might not have been specified).
        await self.auth_handler.set_session_data(
            session_id, "registered_user_id", registered_user_id
        )

        registered = True

    return_dict = await self._create_registration_details(registered_user_id, params)

    if registered:
        await self.registration_handler.post_registration_actions(
            user_id=registered_user_id,
            auth_result=auth_result,
            access_token=return_dict.get("access_token"),
        )

    return 200, return_dict


# Traceback (exceptions.UnboundLocalError)
янв 27 20:08:26 tad python[11795]: 2018-01-27 20:08:26,131 - synapse.http.server - 145 - ERROR - POST-2331- Failed handle request synapse.http.server._async_render on <synapse.rest.ClientRestResource object at 0x7f9e3d39a550>: <SynapseRequest at 0x7f9e3419b368 method=POST uri=/_matrix/client/r0/register?user_id=%40telegram_94477076%3Ahomeserver.ru&amp;access_token=<redacted> clientproto=HTTP/1.0 site=8008>: Traceback (most recent call last):
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 588, in _runCallbacks
янв 27 20:08:26 tad python[11795]:     current.result = callback(current.result, *args, **kw)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1184, in gotResult
янв 27 20:08:26 tad python[11795]:     _inlineCallbacks(r, g, deferred)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1126, in _inlineCallbacks
янв 27 20:08:26 tad python[11795]:     result = result.throwExceptionIntoGenerator(g)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/python/failure.py", line 389, in throwExceptionIntoGenerator
янв 27 20:08:26 tad python[11795]:     return g.throw(self.type, self.value, self.tb)
янв 27 20:08:26 tad python[11795]: --- <exception caught here> ---
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/synapse/http/server.py", line 117, in wrapped_request_handler
янв 27 20:08:26 tad python[11795]:     yield request_handler(self, request, request_metrics)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1126, in _inlineCallbacks
янв 27 20:08:26 tad python[11795]:     result = result.throwExceptionIntoGenerator(g)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/python/failure.py", line 389, in throwExceptionIntoGenerator
янв 27 20:08:26 tad python[11795]:     return g.throw(self.type, self.value, self.tb)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/synapse/http/server.py", line 263, in _async_render
янв 27 20:08:26 tad python[11795]:     callback_return = yield callback(request, **kwargs)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/twisted/internet/defer.py", line 1128, in _inlineCallbacks
янв 27 20:08:26 tad python[11795]:     result = g.send(result)
янв 27 20:08:26 tad python[11795]:   File "/usr/lib/python2.7/dist-packages/synapse/rest/client/v2_alpha/register.py", line 240, in on_POST
янв 27 20:08:26 tad python[11795]:     defer.returnValue((200, result))  # we throw for non 200 responses
янв 27 20:08:26 tad python[11795]: exceptions.UnboundLocalError: local variable 'result' referenced before assignment

# Fixed code
async def on_POST(self, request):
    body = parse_json_object_from_request(request)

    client_addr = request.getClientIP()

    self.ratelimiter.ratelimit(client_addr, update=False)

    kind = b"user"
    if b"kind" in request.args:
        kind = request.args[b"kind"][0]

    if kind == b"guest":
        ret = await self._do_guest_registration(body, address=client_addr)
        return ret
    elif kind != b"user":
        raise UnrecognizedRequestError(
            "Do not understand membership kind: %s" % (kind.decode("utf8"),)
        )

    # Pull out the provided username and do basic sanity checks early since
    # the auth layer will store these in sessions.
    desired_username = None
    if "username" in body:
        if not isinstance(body["username"], str) or len(body["username"]) > 512:
            raise SynapseError(400, "Invalid username")
        desired_username = body["username"]

    appservice = None
    if self.auth.has_access_token(request):
        appservice = self.auth.get_appservice_by_req(request)

    # fork off as soon as possible for ASes which have completely
    # different registration flows to normal users

    # == Application Service Registration ==
    if appservice:
        # Set the desired user according to the AS API (which uses the
        # 'user' key not 'username'). Since this is a new addition, we'll
        # fallback to 'username' if they gave one.
        desired_username = body.get("user", desired_username)

        # XXX we should check that desired_username is valid. Currently
        # we give appservices carte blanche for any insanity in mxids,
        # because the IRC bridges rely on being able to register stupid
        # IDs.

        access_token = self.auth.get_access_token_from_request(request)

        if not isinstance(desired_username, str):
            raise SynapseError(400, "Desired Username is missing or not a string")

        result = await self._do_appservice_registration(
            desired_username, access_token, body
        )

        return 200, result

    # == Normal User Registration == (everyone else)
    if not self._registration_enabled:
        raise SynapseError(403, "Registration has been disabled")

    # For regular registration, convert the provided username to lowercase
    # before attempting to register it. This should mean that people who try
    # to register with upper-case in their usernames don't get a nasty surprise.
    #
    # Note that we treat usernames case-insensitively in login, so they are
    # free to carry on imagining that their username is CrAzYh4cKeR if that
    # keeps them happy.
    if desired_username is not None:
        desired_username = desired_username.lower()

    # Check if this account is upgrading from a guest account.
    guest_access_token = body.get("guest_access_token", None)

    # Pull out the provided password and do basic sanity checks early.
    #
    # Note that we remove the password from the body since the auth layer
    # will store the body in the session and we don't want a plaintext
    # password store there.
    password = body.pop("password", None)
    if password is not None:
        if not isinstance(password, str) or len(password) > 512:
            raise SynapseError(400, "Invalid password")
        self.password_policy_handler.validate_password(password)

    if "initial_device_display_name" in body and password is None:
        # ignore 'initial_device_display_name' if sent without
        # a password to work around a client bug where it sent
        # the 'initial_device_display_name' param alone, wiping out
        # the original registration params
        logger.warning("Ignoring initial_device_display_name without password")
        del body["initial_device_display_name"]

    session_id = self.auth_handler.get_session_id(body)
    registered_user_id = None
    password_hash = None
    if session_id:
        # if we get a registered user id out of here, it means we previously
        # registered a user for this session, so we could just return the
        # user here. We carry on and go through the auth checks though,
        # for paranoia.
        registered_user_id = await self.auth_handler.get_session_data(
            session_id, "registered_user_id", None
        )
        # Extract the previously-hashed password from the session.
        password_hash = await self.auth_handler.get_session_data(
            session_id, "password_hash", None
        )

    # Ensure that the username is valid.
    if desired_username is not None:
        await self.registration_handler.check_username(
            desired_username,
            guest_access_token=guest_access_token,
            assigned_user_id=registered_user_id,
        )

    # Check if the user-interactive authentication flows are complete, if
    # not this will raise a user-interactive auth error.
    try:
        auth_result, params, session_id = await self.auth_handler.check_ui_auth(
            self._registration_flows,
            request,
            body,
            self.hs.get_ip_from_request(request),
            "register a new account",
        )
    except InteractiveAuthIncompleteError as e:
        # The user needs to provide more steps to complete auth.
        #
        # Hash the password and store it with the session since the client
        # is not required to provide the password again.
        #
        # If a password hash was previously stored we will not attempt to
        # re-hash and store it for efficiency. This assumes the password
        # does not change throughout the authentication flow, but this
        # should be fine since the data is meant to be consistent.
        if not password_hash and password:
            password_hash = await self.auth_handler.hash(password)
            await self.auth_handler.set_session_data(
                e.session_id, "password_hash", password_hash
            )
        raise

    # Check that we're not trying to register a denied 3pid.
    #
    # the user-facing checks will probably already have happened in
    # /register/email/requestToken when we requested a 3pid, but that's not
    # guaranteed.
    if auth_result:
        for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
            if login_type in auth_result:
                medium = auth_result[login_type]["medium"]
                address = auth_result[login_type]["address"]

                if not check_3pid_allowed(self.hs, medium, address):
                    raise SynapseError(
                        403,
                        "Third party identifiers (email/phone numbers)"
                        + " are not authorized on this server",
                        Codes.THREEPID_DENIED,
                    )

    if registered_user_id is not None:
        logger.info(
            "Already registered user ID %r for this session", registered_user_id
        )
        # don't re-register the threepids
        registered = False
    else:
        # If we have a password in this request, prefer it. Otherwise, there
        # might be a password hash from an earlier request.
        if password:
            password_hash = await self.auth_handler.hash(password)
        if not password_hash:
            raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)

        desired_username = params.get("username", None)
        guest_access_token = params.get("guest_access_token", None)

        if desired_username is not None:
            desired_username = desired_username.lower()

        threepid = None
        if auth_result:
            threepid = auth_result.get(LoginType.EMAIL_IDENTITY)

            # Also check that we're not trying to register a 3pid that's already
            # been registered.
            #
            # This has probably happened in /register/email/requestToken as well,
            # but if a user hits this endpoint twice then clicks on each link from
            # the two activation emails, they would register the same 3pid twice.
            for login_type in [LoginType.EMAIL_IDENTITY, LoginType.MSISDN]:
                if login_type in auth_result:
                    medium = auth_result[login_type]["medium"]
                    address = auth_result[login_type]["address"]
                    # For emails, canonicalise the address.
                    # We store all email addresses canonicalised in the DB.
                    # (See on_POST in EmailThreepidRequestTokenRestServlet
                    # in synapse/rest/client/v2_alpha/account.py)
                    if medium == "email":
                        try:
                            address = canonicalise_email(address)
                        except ValueError as e:
                            raise SynapseError(400, str(e))

                    existing_user_id = await self.store.get_user_id_by_threepid(
                        medium, address
                    )

                    if existing_user_id is not None:
                        raise SynapseError(
                            400,
                            "%s is already in use" % medium,
                            Codes.THREEPID_IN_USE,
                        )

        entries = await self.store.get_user_agents_ips_to_ui_auth_session(session_id)

        registered_user_id = await self.registration_handler.register_user(
            localpart=desired_username,
            password_hash=password_hash,
            guest_access_token=guest_access_token,
            threepid=threepid,
            address=client_addr,
            user_agent_ips=entries,
        )
        # Necessary due to auth checks prior to the threepid being
        # written to the db
        if threepid:
            if is_threepid_reserved(
                self.hs.config.mau_limits_reserved_threepids, threepid
            ):
                await self.store.upsert_monthly_active_user(registered_user_id)

        # Remember that the user account has been registered (and the user
        # ID it was registered with, since it might not have been specified).
        await self.auth_handler.set_session_data(
            session_id, "registered_user_id", registered_user_id
        )

        registered = True

    return_dict = await self._create_registration_details(registered_user_id, params)

    if registered:
        await self.registration_handler.post_registration_actions(
            user_id=registered_user_id,
            auth_result=auth_result,
            access_token=return_dict.get("access_token"),
        )

    return 200, return_dict