<commit_before>#
# Copyright (c) 2014 ThoughtWorks Deutschland GmbH
#
# Pixelated is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pixelated is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import functools
import traceback
import sys

from tornado import web
from tornado.web import HTTPError
from tornado.httpclient import AsyncHTTPClient
from tornado.httpserver import HTTPServer

from pixelated.client.dispatcher_api_client import PixelatedHTTPError, PixelatedNotAvailableHTTPError
from pixelated.common import logger

import os
import tornado.ioloop
import tornado.web
import tornado.escape
import time
import base64
import uuid
import ssl

from tornado import gen
from pixelated.common import latest_available_ssl_version, DEFAULT_CIPHERS
from tornado.httpclient import AsyncHTTPClient
import threading

COOKIE_NAME = 'pixelated_user'

REQUEST_TIMEOUT = 60
TIMEOUT_WAIT_FOR_AGENT_TO_BE_UP = 5
TIMEOUT_WAIT_STEP = 0.5


class BaseHandler(tornado.web.RequestHandler):

    def initialize(self, client):
        self._client = client

    def prepare(self):
        # add some security headers
        self.set_header('Cache-Control', 'no-cache,no-store,must-revalidate,private')
        self.set_header('Pragma', 'no-cache')
        self.set_header('X-XSS-Protection', '1; mode=block')
        self.set_header('X-Frame-Options', 'DENY')
        self.set_header('X-Content-Type-Options', 'nosniff')

        if self._is_https():
            self.set_header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')

    def _is_https(self):
        return 'https' == self.request.protocol

    def get_current_user(self):
        cookie = self.get_secure_cookie(COOKIE_NAME)
        if cookie:
            return tornado.escape.json_decode(cookie)
        else:
            return None

    def forward(self, port=None, host=None):

        url = "%s://%s:%s%s" % (
            'http', host or "127.0.0.1", port or 80, self.request.uri)
        try:
            response = AsyncHTTPClient().fetch(
                tornado.httpclient.HTTPRequest(
                    url=url,
                    method=self.request.method,
                    body=None if not self.request.body else self.request.body,
                    headers=self.request.headers,
                    follow_redirects=False,
                    request_timeout=REQUEST_TIMEOUT),
                self.handle_response)
            return response
        except tornado.httpclient.HTTPError, x:
            if hasattr(x, 'response') and x.response:
                self.handle_response(x.response)
        except Exception, e:
            logger.error('Error forwarding request %s: %s' % (url, e.message))
            self.set_status(500)
            self.write("Internal server error:\n" + ''.join(traceback.format_exception(*sys.exc_info())))
            self.finish()

    def handle_response(self, response):
        if response.error:
            logger.error('Got error from user %s agent: %s' % (self.current_user, response.error))
            self.set_status(503)
            self.write("Could not connect to instance %s: %s\n" % (self.current_user, str(response.error)))
            self.finish()
        else:
            self.set_status(response.code)
            for header in ("Date", "Cache-Control", 'Pragma', "Server", "Content-Type", "Location"):
                v = response.headers.get(header)
                if v:
                    self.set_header(header, v)

            if response.body:
                self.write(response.body)
            self.finish()

    def logout(self):
        if self.current_user:
            StopServerThread(self._client, self.current_user).start()
        logger.info('User %s logged out' % self.current_user)
        self.clear_cookie(COOKIE_NAME)


def _is_ajax_request(request):
    return 'XMLHttpRequest' == request.headers.get('X-Requested-With')


def ajax_authenticated(method):
    """Browsers transparently handle 302 redirects. For Ajax requests therefore return 401 instead."""

    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        if not self.current_user:
            if _is_ajax_request(self.request):
                self.clear_cookie(COOKIE_NAME)
                raise HTTPError(401)

        return method(self, *args, **kwargs)
    return wrapper


class MainHandler(BaseHandler):
    __slots__ = '_client'

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def get(self):
        runtime = self._client.get_agent_runtime(self.current_user)
        if runtime['state'] == 'running':
            port = runtime['port']
            self.forward(port, '127.0.0.1')
        else:
            self.logout()
            if _is_ajax_request(self.request):
                raise HTTPError(401)
            else:
                logger.error('Agent for %s not running - redirecting user to logout' % self.current_user)
                self.redirect(u'/auth/logout')

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def post(self):
        self.get()

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def put(self):
        self.get()

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def delete(self):
        self.get()

    def check_xsrf_cookie(self):
        # agent should do it after user has logged in
        pass


class AuthLoginHandler(BaseHandler):

    def initialize(self, client, banner):
        super(AuthLoginHandler, self).initialize(client)
        self._banner = banner

    def get(self):
        error_message = self.get_cookie('error_msg')
        if error_message:
            error_message = tornado.escape.url_unescape(error_message)
            self.clear_cookie('error_msg')

        status_message = self.get_cookie('status_msg')
        if status_message:
            status_message = tornado.escape.url_unescape(status_message)
            self.clear_cookie('status_msg')

        self.render('login.html', error=error_message, status=status_message, banner=self._banner)

    @tornado.web.asynchronous
    @gen.coroutine
    def post(self):
        username = self.get_argument("username", "")
        password = self.get_argument("password", "")
        try:
            agent = self._client.get_agent(username)

            # now authenticate with server...<commit_msg>Issue pixelated-user-agent#581 - fixed the bug<commit_after>#
# Copyright (c) 2014 ThoughtWorks Deutschland GmbH
#
# Pixelated is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pixelated is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
import functools
import traceback
import sys

from tornado import web
from tornado.web import HTTPError
from tornado.httpclient import AsyncHTTPClient
from tornado.httpserver import HTTPServer

from pixelated.client.dispatcher_api_client import PixelatedHTTPError, PixelatedNotAvailableHTTPError
from pixelated.common import logger

import os
import tornado.ioloop
import tornado.web
import tornado.escape
import time
import base64
import uuid
import ssl

from tornado import gen
from pixelated.common import latest_available_ssl_version, DEFAULT_CIPHERS
from tornado.httpclient import AsyncHTTPClient
import threading

COOKIE_NAME = 'pixelated_user'

REQUEST_TIMEOUT = 60
TIMEOUT_WAIT_FOR_AGENT_TO_BE_UP = 5
TIMEOUT_WAIT_STEP = 0.5


class BaseHandler(tornado.web.RequestHandler):

    def initialize(self, client):
        self._client = client

    def prepare(self):
        # add some security headers
        self.set_header('Cache-Control', 'no-cache,no-store,must-revalidate,private')
        self.set_header('Pragma', 'no-cache')
        self.set_header('X-XSS-Protection', '1; mode=block')
        self.set_header('X-Frame-Options', 'DENY')
        self.set_header('X-Content-Type-Options', 'nosniff')

        if self._is_https():
            self.set_header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')

    def _is_https(self):
        return 'https' == self.request.protocol

    def get_current_user(self):
        cookie = self.get_secure_cookie(COOKIE_NAME)
        if cookie:
            return tornado.escape.json_decode(cookie)
        else:
            return None

    def forward(self, port=None, host=None):

        url = "%s://%s:%s%s" % (
            'http', host or "127.0.0.1", port or 80, self.request.uri)
        try:
            response = AsyncHTTPClient().fetch(
                tornado.httpclient.HTTPRequest(
                    url=url,
                    method=self.request.method,
                    body=None if not self.request.body else self.request.body,
                    headers=self.request.headers,
                    follow_redirects=False,
                    request_timeout=REQUEST_TIMEOUT),
                self.handle_response)
            return response
        except tornado.httpclient.HTTPError, x:
            if hasattr(x, 'response') and x.response:
                self.handle_response(x.response)
        except Exception, e:
            logger.error('Error forwarding request %s: %s' % (url, e.message))
            self.set_status(500)
            self.write("Internal server error:\n" + ''.join(traceback.format_exception(*sys.exc_info())))
            self.finish()

    def handle_response(self, response):
        if response.error:
            logger.error('Got error from user %s agent: %s' % (self.current_user, response.error))
            self.set_status(503)
            self.write("Could not connect to instance %s: %s\n" % (self.current_user, str(response.error)))
            self.finish()
        else:
            self.set_status(response.code)
            for header in ("Date", "Cache-Control", 'Pragma', "Server", "Content-Type", "Location"):
                v = response.headers.get(header)
                if v:
                    self.set_header(header, v)

            if response.body:
                self.write(response.body)
            self.finish()

    def logout(self):
        if self.current_user:
            StopServerThread(self._client, self.current_user).start()
        logger.info('User %s logged out' % self.current_user)
        self.clear_cookie(COOKIE_NAME)


def _is_ajax_request(request):
    return 'XMLHttpRequest' == request.headers.get('X-Requested-With')


def ajax_authenticated(method):
    """Browsers transparently handle 302 redirects. For Ajax requests therefore return 401 instead."""

    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        if not self.current_user:
            if _is_ajax_request(self.request):
                self.clear_cookie(COOKIE_NAME)
                raise HTTPError(401)

        return method(self, *args, **kwargs)
    return wrapper


class MainHandler(BaseHandler):
    __slots__ = '_client'

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def get(self):
        runtime = self._client.get_agent_runtime(self.current_user)
        if runtime['state'] == 'running':
            port = runtime['port']
            self._add_header_if_is_download_attachment()
            self.forward(port, '127.0.0.1')
        else:
            self.logout()
            if _is_ajax_request(self.request):
                raise HTTPError(401)
            else:
                logger.error('Agent for %s not running - redirecting user to logout' % self.current_user)
                self.redirect(u'/auth/logout')

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def post(self):
        self.get()

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def put(self):
        self.get()

    @ajax_authenticated
    @tornado.web.authenticated
    @tornado.web.asynchronous
    def delete(self):
        self.get()

    def check_xsrf_cookie(self):
        # agent should do it after user has logged in
        pass

    def _add_header_if_is_download_attachment(self):
        if self.request.arguments.get('filename'):
            self.add_header('Content-Disposition', 'attachment; filename=' + self.request.arguments.get('filename')[0])


class AuthLoginHandler(BaseHandler):

    def initialize(self, client, banner):
        super(AuthLoginHandler, self).initialize(client)
        self._banner = banner

    def get(self):
        error_message = self.get_cookie('error_msg')
        if error_message:
            error_message = tornado.escape.url_unescape(error_message)
            self.clear_cookie('error_msg')

        status_message = self.get_cookie('status_msg')
        if status_message:
            status_message = tornado.escape.url_unescape(status_message)
            self.clear_cookie('status_msg')

        self.render('login.html', error=error_message, status=status_message, banner=self._banner)

    @tornado.web.asynchronous
    @gen.coroutine
    def post(self):
        username = self.get_argument("username", "")
        password = self.get_argument("password", "")
        try:
            agent = self._client.get_agent(username)

            # now authenticate with server...
{'fraction_non_alphanumeric': 0.09840556296972677, 'fraction_numerical': 0.007726346842733722, 'mean_word_length': 2.636781609195402, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7029571', 'n_tokens_mistral': 4232, 'n_tokens_neox': 4032, 'n_words': 982}