async def renew_token(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse:
        try:
            token = header.cookie(scope['headers']).get(self.cookie_name)
            if not token:
                # Unauthorised
                return text_response(401, [], 'Client requires authentication')

            payload = jwt.decode(token, key=self.secret, options={'verify_exp': False})

            user = payload['sub']
            issued_at = datetime.utcfromtimestamp(payload['iat'])

            logger.debug(f'Token renewal request: user={user}, iat={issued_at}')

            utc_now = datetime.utcnow()

            authentication_expiry = issued_at + self.login_expiry
            if utc_now > authentication_expiry:
                # Unauthenticated
                logger.debug(f'Token expired for user {user} issued at {issued_at} expired at {authentication_expiry}')
                return text_response(401, [], 'Authentication expired')

            if not self.auth_service.is_valid(user):
                return 403, None, None  # Forbidden

            logger.debug(f'Token renewed for {user}')
            token = self._make_token(user, utc_now, issued_at=issued_at)
            logger.debug(f'Sending token {token}')

            set_cookie = self._make_cookie(token)

            return 204, [(b'set-cookie', set_cookie)], None

        except:
            return 500, None, None
    async def login_post(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse:
        try:
            query = parse_qs(scope['query_string'])
            redirect = query.get(b'redirect')
            if not redirect:
                logger.debug('No redirect')
                return text_response(404, [], 'No redirect')
            redirect = redirect[0]

            text = await text_reader(content)
            body = parse_qs(text)
            username = body['username'][0]
            password = body['password'][0]

            if not self.auth_service.is_password_for_user(username, password):
                raise RuntimeError('Invalid username or password')

            now = datetime.utcnow()
            token = self._make_token(body['username'], now, issued_at=int(mktime(now.timetuple())))

            logger.debug(f'Sending token: {token}')
            urlparts = urlparse(redirect)
            if urlparts.scheme is None or len(urlparts.scheme) == 0:
                raise RuntimeError('The redirect URL has no scheme')

            set_cookie = self._make_cookie(token)

            return 302, [(b'set-cookie', set_cookie), (b'location', redirect)], None

        except:
            logger.exception('Failed to log in')
            return 302, [(b'location', header.find(b'referer', scope['headers']))], None
    async def login_post(
            self,
            scope: Scope,
            info: Info,
            matches: RouteMatches,
            content: Content
    ) -> HttpResponse:
        """A login POST request handler

        :param scope: The ASGI scope
        :type scope: Scope
        :param info: The application shared info
        :type info: Info
        :param matches: The route matches
        :type matches: RouteMatches
        :param content: The ASGI content
        :type content: Content
        :return: A login response
        :rtype: HttpResponse
        """
        try:
            query = parse_qs(scope['query_string'])
            redirect = query.get(b'redirect')
            if not redirect:
                logger.debug('No redirect')
                return text_response(response_code.NOT_FOUND, None, 'No redirect')
            redirect = redirect[0]

            text = await text_reader(content)
            body = parse_qs(text)
            username = body['username'][0]
            password = body['password'][0]

            if not await self.authentication_service.is_password_for_user(username, password):
                raise RuntimeError('Invalid username or password')

            now = datetime.utcnow()
            token = self.token_manager.encode(username, now, now)

            logger.debug('Sending token: %s', token)
            urlparts = urlparse(redirect)
            if urlparts.scheme is None or not urlparts.scheme:
                raise RuntimeError('The redirect URL has no scheme')

            set_cookie = self.token_manager.make_cookie(token)

            return response_code.FOUND, [(b'set-cookie', set_cookie), (b'location', redirect)]

        except:  # pylint: disable=bare-except
            logger.exception('Failed to log in')
            location = header.find(b'referer', scope['headers'])
            return response_code.FOUND, [(b'location', location)]
    async def who_am_i(self, scope: Scope, info: Info, matches: RouteMatches, content: Content) -> HttpResponse:
        try:
            token = header.cookie((scope['headers'])).get(self.cookie_name)
            if token is None:
                return text_response(401, [], 'Client requires authentication')

            payload = jwt.decode(token, key=self.secret)

            return json_response(200, [], {'username': payload['sub']})
        except (jwt.exceptions.ExpiredSignature, PermissionError) as error:
            logger.debug(f'JWT encoding failed: {error}')
            return 401, None, None
        except:
            logger.exception(f'Failed to re-sign the token')
            return 500, None, None
    async def who_am_i(
            self,
            scope: Scope,
            info: Info,
            matches: RouteMatches,
            content: Content
    ) -> HttpResponse:
        """Returns the login status of the user

        :param scope: The ASGI scope
        :type scope: Scope
        :param info: The application shared info
        :type info: Info
        :param matches: The route matches
        :type matches: RouteMatches
        :param content: The ASGI content
        :type content: Content
        :return: A whoami response
        :rtype: HttpResponse
        """
        try:
            token = self.token_manager.get_token_from_headers(scope['headers'])
            if token is None:
                return text_response(
                    response_code.UNAUTHORIZED,
                    None,
                    'Client requires authentication'
                )

            payload = self.token_manager.decode(token)

            return json_response(response_code.OK, None, {'username': payload['sub']})
        except (jwt.exceptions.ExpiredSignature, PermissionError):
            logger.exception('JWT encoding failed')
            return response_code.UNAUTHORIZED
        except:  # pylint: disable=bare-except
            logger.exception('Failed to re-sign the token')
            return response_code.INTERNAL_SERVER_ERROR
Пример #6
0
def text_response(text, status=200, headers={}):
    headers = []
    return _bareasgi.text_response(status, headers, text)
    async def renew_token(
            self,
            scope: Scope,
            info: Info,
            matches: RouteMatches,
            content: Content
    ) -> HttpResponse:
        """Renew the token

        :param scope: The ASGI scope
        :type scope: Scope
        :param info: The application shared info
        :type info: Info
        :param matches: The route matches
        :type matches: RouteMatches
        :param content: The ASGI content
        :type content: Content
        :return: A no-content response with the cookie in the header.
        :rtype: HttpResponse
        """
        try:
            token = self.token_manager.get_token_from_headers(scope['headers'])
            if not token:
                return text_response(
                    response_code.UNAUTHORIZED,
                    None,
                    'Client requires authentication'
                )

            payload = self.token_manager.decode(token)

            user = payload['sub']
            issued_at = payload['iat']

            logger.debug(
                'Token renewal request: user=%s, iat=%s',
                user,
                issued_at
            )

            utc_now = datetime.utcnow()

            authentication_expiry = issued_at + self.login_expiry
            if utc_now > authentication_expiry:
                logger.debug(
                    'Token expired for user %s issued at %s expired at %s',
                    user,
                    issued_at,
                    authentication_expiry
                )
                return text_response(response_code.UNAUTHORIZED, None, 'Authentication expired')

            if not self.authentication_service.is_valid(user):
                return response_code.FORBIDDEN, None, None

            logger.debug('Token renewed for %s', user)
            token = self.token_manager.encode(user, utc_now, issued_at)
            logger.debug('Sending token %s', token)

            set_cookie = self.token_manager.make_cookie(token)

            return response_code.NO_CONTENT, [(b'set-cookie', set_cookie)], None

        except:  # pylint: disable=bare-except
            logger.exception('Failed to renew token')
            return response_code.INTERNAL_SERVER_ERROR, None, None