Exemplo n.º 1
0
def test_upsert():
    """Test upsert"""
    headers = [(b'content-type', b'application/json'),
               (b'vary', b'accept-encoding, user-agent'),
               (b'cookie', b'one=first; two=second; three=third;')]

    header.upsert(b'content-type', b'text/plain', headers)
    assert len(header.find_all(b'content-type', headers)) == 1
    assert header.find(b'content-type', headers) == b'text/plain'

    header.upsert(b'content-encoding', b'gzip', headers)
    assert len(header.find_all(b'content-encoding', headers)) == 1
    assert header.find(b'content-encoding', headers) == b'gzip'
async def get_info(scope, info, matches, content):
    accept = header.find(b'accept', scope['headers'])
    if accept != b'application/json':
        return 500
    text = json.dumps(info)
    headers = [(b'content-type', b'application/json')]
    return 200, headers, text_writer(text)
Exemplo n.º 3
0
async def test_page(scope: Scope, info: Info, _matches: RouteMatches,
                    _content: Content) -> HttpResponse:
    """A request handler which provides the page to respond to server sent events"""
    host = header.find(b'host', scope['headers']).decode()
    fetch_url = f"{scope['scheme']}://{host}/events"
    html = info['page_template'].substitute(__FETCH_URL__=fetch_url)
    return 200, [(b'content-type', b'text/html')], text_writer(html)
Exemplo n.º 4
0
async def graphql_handler(request: HttpRequest) -> HttpResponse:
    """Handle a graphql request"""
    host = header.find(b'host', request.scope['headers']).decode()
    sse_url = f"{request.scope['scheme']}://{host}/sysmon/graphql"
    html = request.info['page_template'].substitute(sse_url=sse_url)
    return HttpResponse(200, [(b'content-type', b'text/html')],
                        text_writer(html))
    async def _renew_cookie(
            self,
            scope: Scope,
            token: bytes
    ) -> Optional[bytes]:

        scheme = get_scheme(scope).decode('ascii')
        host = get_host(scope).decode('ascii')

        referer_url = f'{scheme}://{host}{scope["path"]}'
        if scope['query_string']:
            referer_url += '?' + scope['query_string'].decode('utf-8')
        referer = header.find(
            b'referer',
            scope['headers'],
            referer_url.encode('ascii')
        )

        headers: List[Header] = [
            (b'host', host.encode('ascii')),
            (b'referer', referer),
            (b'content-length', b'0'),
            (b'connection', b'close')
        ]
        if token is not None:
            cookie = self.token_manager.cookie_name + b'=' + token
            headers.append((b'cookie', cookie))

        renewal_url = f'{scheme}://{host}{self.token_renewal_path}'

        logger.debug(
            'Renewing cookie at %s with headers %s',
            renewal_url,
            headers
        )

        async with HttpClient(
                renewal_url,
                method='POST',
                headers=headers,
                cafile=self.cafile
        ) as response:

            if response['status_code'] == response_code.NO_CONTENT:
                logger.debug('Cookie renewed')
                all_set_cookies = header.set_cookie(response['headers'])
                auth_set_cookies = all_set_cookies.get(
                    self.token_manager.cookie_name)
                if auth_set_cookies is None:
                    raise RuntimeError('No cookie returned')
                kwargs = auth_set_cookies[0]
                set_cookie = encode_set_cookie(**kwargs)
                return set_cookie
            elif response['status_code'] == response_code.UNAUTHORIZED:
                logger.debug(
                    'Cookie not renewed - client requires authentication')
                return None
            else:
                logger.debug('Cookie not renewed - failed to authenticate')
                raise Exception()
Exemplo n.º 6
0
def _is_not_modified(request_headers: Iterable[Tuple[bytes, bytes]],
                     response_headers: Iterable[Tuple[bytes, bytes]]) -> bool:
    if request_headers is None or response_headers is None:
        return False
    etag = header.find(b'etag', response_headers)
    last_modified = header.find(b'last-modified', response_headers)
    assert last_modified is not None
    if etag == header.find(b'if-none-match', request_headers):
        return True
    if not header.find(b'if-modified-since', request_headers):
        return False
    last_req_time = header.find(b'if-modified-since', request_headers)
    assert last_req_time is not None
    last_req = cast(struct_time, parsedate(last_req_time.decode()))
    last_modified_struct_time = cast(struct_time,
                                     parsedate(last_modified.decode()))
    return mktime(last_req) >= mktime(last_modified_struct_time)
async def set_info(scope, info, matches, content):
    content_type = header.find(b'content-type', scope['headers'])
    if content_type != b'application/json':
        return 500
    text = await text_reader(content)
    data = json.loads(text)
    info.update(data)
    return 204
    async def _handle_streaming_subscription(
            self, request: HttpRequest, query: str,
            variables: Optional[Dict[str, Any]],
            operation_name: Optional[str]) -> HttpResponse:
        # If unspecified default to server sent events as they have better support.
        accept = cast(
            bytes,
            header.find(b'accept', request.scope['headers'],
                        b'text/event-stream'))
        content_type = (b'application/stream+json'
                        if accept == b'application/json' else accept)

        result = await self.subscribe(request, query, variables,
                                      operation_name)

        is_sse = content_type == b'text/event-stream'
        encode = partial(_encode_sse if is_sse else _encode_json, self.dumps)
        nudge = b':\n\n' if is_sse else b'\n'

        # Make an async iterator for the subscription results.
        async def send_events(zero_event: ZeroEvent) -> AsyncIterable[bytes]:
            LOGGER.debug('Streaming subscription started.')

            try:
                zero_event.increment()

                async for val in cancellable_aiter(result,
                                                   self.cancellation_event,
                                                   timeout=self.ping_interval):
                    yield encode(val)
                    yield nudge  # Give the ASGI server a nudge.

            except asyncio.CancelledError:
                LOGGER.debug("Streaming subscription cancelled.")
            except Exception as error:  # pylint: disable=broad-except
                LOGGER.exception("Streaming subscription failed.")
                # If the error is not caught the client fetch will fail, however
                # the status code and headers have already been sent. So rather
                # than let the fetch fail we send a GraphQL response with no
                # data and the error and close gracefully.
                if not isinstance(error, GraphQLError):
                    error = GraphQLError('Execution error',
                                         original_error=error)
                val = ExecutionResult(None, [error])
                yield encode(val)
                yield nudge  # Give the ASGI server a nudge.
            finally:
                zero_event.decrement()

            LOGGER.debug("Streaming subscription stopped.")

        headers = [(b'cache-control', b'no-cache'),
                   (b'content-type', content_type),
                   (b'connection', b'keep-alive')]

        return HttpResponse(response_code.OK, headers,
                            send_events(self.subscription_count))
async def get_info(scope: Scope, info: Info, _matches: RouteMatches,
                   _content: Content) -> HttpResponse:
    """Handle the GET request"""
    accept = header.find(b'accept', scope['headers'])
    if accept != b'application/json':
        return 500
    text = json.dumps(info)
    headers = [(b'content-type', b'application/json')]
    return 200, headers, text_writer(text)
Exemplo n.º 10
0
async def set_info(scope: Scope, info: Info, _matches: RouteMatches,
                   content: Content) -> HttpResponse:
    """Handle the POST request"""
    content_type = header.find(b'content-type', scope['headers'])
    if content_type != b'application/json':
        return 500
    text = await text_reader(content)
    data = json.loads(text)
    info.update(data)
    return 204
    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)]
Exemplo n.º 12
0
async def websocket_page(scope, info, matches, content):
    scheme = 'wss' if scope['scheme'] == 'https' else 'ws'
    if scope['http_version'] in ('2', '2.0'):
        authority = header.find(b':authority',
                                scope['headers']).decode('ascii')
    else:
        host, port = scope['server']
        authority = f'{host}:{port}'
    web_socket_url = f"{scheme}://{authority}/websocket_handler"
    print(web_socket_url)

    page = info['html'].replace('WEB_SOCKET_URL', web_socket_url)
    return 200, [(b'content-type', b'text/html')], text_writer(page)
Exemplo n.º 13
0
async def post_form(scope, info, matches, content):
    content_type = header.find(b'content-type', scope['headers'])
    if content_type != b'application/x-www-form-urlencoded':
        return 500

    variables = await text_reader(content)
    values = dict(parse_qsl(variables))
    first_name = values['first_name']
    last_name = values['last_name']

    headers = [
        (b'location', b'/get_form'),
        (b'set-cookie', f'first_name={first_name}'.encode()),
        (b'set-cookie', f'last_name={last_name}'.encode()),
    ]
    return 303, headers
 def _handle_subscription_redirect(self, request: HttpRequest,
                                   body: Mapping[str, Any]) -> HttpResponse:
     # Handle a subscription by returning 201 (Created) with
     # the url location of the subscription.
     LOGGER.debug("Redirecting subscription request.")
     scheme = request.scope['scheme']
     host = cast(
         bytes,
         header.find(  # type: ignore
             b'host', request.scope['headers'], b'localhost')).decode()
     path = self.path_prefix + '/subscriptions'
     query_string = urlencode({
         name.encode('utf-8'): self.dumps(value).encode('utf-8')
         for name, value in body.items()
     })
     location = f'{scheme}://{host}{path}?{query_string}'.encode('ascii')
     headers = [(b'access-control-expose-headers', b'location'),
                (b'location', location)]
     return HttpResponse(response_code.CREATED, headers)
Exemplo n.º 15
0
async def post_form(
        scope: Scope,
        _info: Info,
        _matches: RouteMatches,
        content: Content
) -> HttpResponse:
    """A response handler that reads the cookies from a posted form."""
    content_type = header.find(b'content-type', scope['headers'])
    if content_type != b'application/x-www-form-urlencoded':
        return 500
    variables = await text_reader(content)
    values: Dict[str, str] = dict(parse_qsl(variables))
    first_name = values['first_name']
    last_name = values['last_name']

    headers = [
        (b'location', b'/get_form'),
        (b'set-cookie', f'first_name={first_name}'.encode()),
        (b'set-cookie', f'last_name={last_name}'.encode()),
    ]
    return 303, headers
    async def handle_graphql(self, request: HttpRequest) -> HttpResponse:
        """A request handler for graphql queries

        Args:
            scope (Scope): The Request

        Returns:
            HttpResponse: The HTTP response to the query request
        """

        try:
            body = await self._get_query_document(request)

            query: str = body['query']
            variables: Optional[Dict[str, Any]] = body.get('variables')
            operation_name: Optional[str] = body.get('operationName')

            query_document = graphql.parse(query)

            if not has_subscription(query_document):
                return await self._handle_query_or_mutation(
                    request, query, variables, operation_name)

            # The subscription method is determined by the `allow` header.
            allow = header.find(b'allow', request.scope['headers'], b'GET')
            if allow == b'GET':
                return self._handle_subscription_redirect(request, body)

            return await self._handle_streaming_subscription(
                request, query, variables, operation_name)

        # pylint: disable=bare-except
        except:
            LOGGER.exception("Failed to handle graphql query request")

            text = 'Internal server error'
            headers = [(b'content-type', b'text/plain'),
                       (b'content-length', str(len(text)).encode())]
            return HttpResponse(response_code.INTERNAL_SERVER_ERROR, headers,
                                text_writer(text))
Exemplo n.º 17
0
async def test_page(
        scope: Scope,
        _info: Info,
        _matches: RouteMatches,
        _content: Content
) -> HttpResponse:
    """Send the page with the example web socket"""
    scheme = 'wss' if scope['scheme'] == 'https' else 'ws'
    if scope['http_version'] in ('2', '2.0'):
        authority = header.find(
            b':authority', scope['headers']).decode('ascii')
    else:
        host, port = scope['server']
        authority = f'{host}:{port}'
    web_socket_url = f"{scheme}://{authority}/test"
    print(web_socket_url)

    page = """
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>WebSocket Example</title>
        <style>
*, *:before, *:after {{
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}}

html {{
  font-family: Helvetica, Arial, sans-serif;
  font-size: 100%;
  background: #333;
}}

#page-wrapper {{
  width: 650px;
  background: #FFF;
  padding: 1em;
  margin: 1em auto;
  border-top: 5px solid #69c773;
  box-shadow: 0 2px 10px rgba(0,0,0,0.8);
}}

h1 {{
	margin-top: 0;
}}

#status {{
  font-size: 0.9rem;
  margin-bottom: 1rem;
}}

.open {{
  color: green;
}}

.closed {{
  color: red;
}}


ul {{
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.95rem;
}}

ul li {{
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid #EEE;
}}

ul li:first-child {{
  border-top: 1px solid #EEE;
}}

ul li span {{
  display: inline-block;
  width: 90px;
  font-weight: bold;
  color: #999;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 1px;
}}

.sent {{
  background-color: #F7F7F7;
}}

.received {{}}

#message-form {{
  margin-top: 1.5rem;
}}

textarea {{
  width: 100%;
  padding: 0.5rem;
  font-size: 1rem;
  border: 1px solid #D9D9D9;
  border-radius: 3px;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
  min-height: 100px;
  margin-bottom: 1rem;
}}

button {{
  display: inline-block;
  border-radius: 3px;
  border: none;
  font-size: 0.9rem;
  padding: 0.6rem 1em;
  color: white;
  margin: 0 0.25rem;
  text-align: center;
  background: #BABABA;
  border-bottom: 1px solid #999;
}}

button[type="submit"] {{
  background: #86b32d;
  border-bottom: 1px solid #5d7d1f;
}}

button:hover {{
  opacity: 0.75;
  cursor: pointer;
}}        
        </style>
      </head>
      <body>
      
        <div id="page-wrapper">
          <h1>WebSockets Demo</h1>
  
          <div id="status">Connecting...</div>
  
          <ul id="messages"></ul>
  
          <form id="message-form" action="#" method="post">
            <textarea id="message" placeholder="Write your message here..." required></textarea>
            <button type="submit">Send Message</button>
            <button type="button" id="close">Close Connection</button>
          </form>
        </div>
        
        <script language="javascript" type="text/javascript">

window.onload = function() {{

  // Get references to elements on the page.
  var form = document.getElementById('message-form');
  var messageField = document.getElementById('message');
  var messagesList = document.getElementById('messages');
  var socketStatus = document.getElementById('status');
  var closeBtn = document.getElementById('close');


  // Create a new WebSocket.
  var socket = new WebSocket('{web_socket_url}');


  // Handle any errors that occur.
  socket.onerror = function(error) {{
    console.log('WebSocket Error: ' + error);
  }};


  // Show a connected message when the WebSocket is opened.
  socket.onopen = function(event) {{
    socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.url;
    socketStatus.className = 'open';
  }};


  // Handle messages sent by the server.
  socket.onmessage = function(event) {{
    var message = event.data;
    messagesList.innerHTML += '<li class="received"><span>Received:</span>' + message + '</li>';
  }};


  // Show a disconnected message when the WebSocket is closed.
  socket.onclose = function(event) {{
    socketStatus.innerHTML = 'Disconnected from WebSocket.';
    socketStatus.className = 'closed';
  }};


  // Send a message when the form is submitted.
  form.onsubmit = function(e) {{
    e.preventDefault();

    // Retrieve the message from the textarea.
    var message = messageField.value;

    // Send the message through the WebSocket.
    socket.send(message);

    // Add the message to the messages list.
    messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message + '</li>';

    // Clear out the message field.
    messageField.value = '';

    return false;
  }};


  // Close the WebSocket connection when the close button is clicked.
  closeBtn.onclick = function(e) {{
    e.preventDefault();

    // Close the WebSocket.
    socket.close();

    return false;
  }};

}};
        </script>
      </body>
    </html>
    """.format(web_socket_url=web_socket_url)
    return 200, [(b'content-type', b'text/html')], text_writer(page)
async def test_page(scope, info, matches, content):
    host = header.find(b'host', scope['headers']).decode()
    fetch_url = f"{scope['scheme']}://{host}/events"
    html = info['page_template'].substitute(__FETCH_URL__=fetch_url)
    return 200, [(b'content-type', b'text/html')], text_writer(html)
Exemplo n.º 19
0
def make_url(scope) -> str:
    host = header.find(b'host', scope['headers'], b'unknown').decode()
    return f"{scope['scheme']}://{host}{scope['path']}"
Exemplo n.º 20
0
def make_url(scope: Scope) -> str:
    """Make the url from the scope"""
    host = header.find(b'host', scope['headers'], b'unknown').decode()
    return f"{scope['scheme']}://{host}{scope['path']}"
Exemplo n.º 21
0
async def index(scope: Scope, _info: Info, _matches: RouteMatches,
                _content: Content) -> HttpResponse:
    """The Websocket page"""

    scheme = 'wss' if scope['scheme'] == 'https' else 'ws'
    if scope['http_version'] in ('2', '2.0'):
        authority = header.find(b':authority',
                                scope['headers']).decode('ascii')
    else:
        host, port = scope['server']
        authority = f'{host}:{port}'
    web_socket_url = f"{scheme}://{authority}/test/websocket"

    html = """
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Websocket Example</title>
  </head>
  <body>
    <h1>Websocket Example</h1>

    <div id="status">Connecting...</div>

    <ul id="messages"></ul>

    <form id="message-form" action="#" method="post">
      <textarea id="message" placeholder="Write your message here..." required></textarea>
      <button type="submit">Send Message</button>
      <button type="button" id="close">Close Connection</button>
    </form>

    <script language="javascript" type="text/javascript">

    window.onload = function() {{

      // Get references to elements on the page.
      var form = document.getElementById('message-form')
      var messageField = document.getElementById('message')
      var messagesList = document.getElementById('messages')
      var socketStatus = document.getElementById('status')
      var closeBtn = document.getElementById('close')

      // Create a new WebSocket.
      var socket = new WebSocket('{web_socket_url}')
  
      // Handle any errors that occur.
      socket.onerror = function(error) {{
        console.log('WebSocket Error: ' + error)
      }}

      // Show a connected message when the WebSocket is opened.
      socket.onopen = function(event) {{
        socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.url
        socketStatus.className = 'open'
      }}

      // Handle messages sent by the server.
      socket.onmessage = function(event) {{
        var message = event.data
        messagesList.innerHTML += '<li class="received"><span>Received:</span>' + message + '</li>'
      }}

      // Show a disconnected message when the WebSocket is closed.
      socket.onclose = function(event) {{
        socketStatus.innerHTML = 'Disconnected from WebSocket.'
        socketStatus.className = 'closed'
      }}

      // Send a message when the form is submitted.
      form.onsubmit = function(e) {{
        e.preventDefault()

        // Retrieve the message from the textarea.
        var message = messageField.value

        // Send the message through the WebSocket.
        socket.send(message)

        // Add the message to the messages list.
        messagesList.innerHTML += '<li class="sent"><span>Sent:</span>' + message + '</li>'

        // Clear out the message field.
        messageField.value = ''

        return false
      }}

      // Close the WebSocket connection when the close button is clicked.
      closeBtn.onclick = function(e) {{
        e.preventDefault()

        // Close the WebSocket.
        socket.close()

        return false
      }}

    }}
    </script>

  </body>
</html>
""".format(web_socket_url=web_socket_url)
    return 200, [(b'content-type', b'text/html')], text_writer(html)