def _login(self): self.getPage('/login', method='POST', body='login=admin&password=admin') cookie = BaseCookie() cookie.load(self.cookies[0][1]) return cookie['session_id'].value
def unset_cookie(self, name): """Remove a cookie from those that are being sent with the response""" name = isomorphic_decode(name) cookies = self.headers.get("Set-Cookie") parser = BaseCookie() for cookie in cookies: parser.load(isomorphic_decode(cookie)) if name in parser.keys(): del self.headers["Set-Cookie"] for m in parser.values(): if m.key != name: self.headers.append(("Set-Cookie", m.OutputString()))
def logged_in(response, session_cookie_name): # This doesn't work because sessions are now created for guests. cookie_text = response.headers.get("set-cookie", None) if not cookie_text: return False cookies = BaseCookie() cookies.load(cookie_text) morsel = cookies.get(session_cookie_name, None) value = morsel and morsel.value # Cannot test to see if secure cookie can be parsed as JSON. if value: try: value = json.loads(value) except json.decoder.JSONDecodeError: print( "Unable to parse value of cookie `%s` as JSON: %s." % ( session_cookie_name, repr(value))) return bool(value)
async def handleRequest(self, reader, writer): # connection id, for debugging connid = self.nextConnId self.nextConnId += 1 logger.debug(f'{connid}: new incoming connection') # simple HTTP parsing, yes this is a terrible idea. l = await reader.readline() if l == bytes(): raise BadRequest(f'{connid}: unexpected eof') l = l.rstrip(b'\r\n') try: method, rawPath, proto = l.split(b' ') logger.debug(f'{connid}: got {method} {rawPath} {proto}') except ValueError: logger.error(f'{connid}: cannot split line {l}') raise reqUrl = furl(rawPath.decode('utf-8')) headers = CIMultiDict() while True: if len(headers) > 100: raise BadRequest('too many headers') l = await reader.readline() if l == bytes(): raise BadRequest(f'{connid}: unexpected eof in headers') l = l.rstrip(b'\r\n') logger.debug(f'{connid}: got header line {l!r}') # end of headers? if l == bytes(): break try: key, value = l.decode('utf-8').split(':', 1) headers.add(key.strip(), value.strip()) except ValueError: logger.error(f'cannot parse {l}') logger.debug(f'{connid}: {rawPath} {method} got headers {headers}') route = None try: netloc = headers['host'] reqUrl = reqUrl.set(scheme='http', netloc=netloc) logger.debug(f'got request url {reqUrl}') routeKey = None for d in self.domain: m = parse(d, reqUrl.netloc) if m is not None: routeKey = RouteKey(key=m['key'], user=m['user']) logger.debug(f'{connid}: got route key {routeKey}') break route = self.routes[routeKey] except (KeyError, ValueError): logger.info(f'{connid}: cannot find route for {reqUrl}') self.status['noroute'] += 1 # error is written to client later # is this a non-forwarded request? segments = reqUrl.path.segments if len(segments) > 0 and segments[0] == '_conductor': if segments[1] == 'auth': logger.info(f'authorization request for {reqUrl.netloc}') try: nextLoc = reqUrl.query.params['next'].encode('utf-8') except KeyError: nextLoc = b'/' writer.write(b'\r\n'.join([ b'HTTP/1.0 302 Found', b'Location: ' + nextLoc, b'Set-Cookie: authorization=' + segments[2].encode('utf-8') + b'; HttpOnly; Path=/', b'Cache-Control: no-store', b'', b'Follow the white rabbit.' ])) elif segments[1] == 'status': writer.write( b'HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n' ) self.status['routesTotal'] = len(self.routes) writer.write( json.dumps(self.status, ensure_ascii=True).encode('ascii')) else: writer.write( b'HTTP/1.0 404 Not Found\r\nContent-Type: plain/text\r\n\r\nNot found' ) writer.close() return if not route: writer.write( b'HTTP/1.0 404 Not Found\r\nConnection: close\r\n\r\n') writer.close() return # check authorization cookies = BaseCookie() try: cookies.load(headers['Cookie']) except KeyError: # will be rejected later pass authorized = False for c in cookies.values(): # Only hashed authorization is available to server. if c.key == 'authorization' and self.hashKey( c.value) == route.auth: authorized = True break try: # do not forward auth cookie to the application, so it can’t leak it. del cookies['authorization'] headers['Cookie'] = cookies.output(header='', sep='') except KeyError: # nonexistent cookie is fine pass if not authorized: logger.info( f'{connid}-{reqUrl}: not authorized, cookies sent {cookies.values()}' ) writer.write( b'HTTP/1.0 403 Unauthorized\r\nContent-Type: plain/text\r\nConnection: close\r\n\r\nUnauthorized' ) writer.close() self.status['unauthorized'] += 1 return # try opening the socket try: start = time.time() sockreader, sockwriter = await asyncio.open_unix_connection( path=route.socket) end = time.time() logger.debug(f'opening socket took {end-start}s') except (ConnectionRefusedError, FileNotFoundError, PermissionError): logger.info(f'{connid}-{reqUrl}: route {routeKey} is broken') writer.write( b'HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\n') writer.close() self.status['broken'] += 1 return # some headers are fixed # not parsing body, so we cannot handle more than one request per connection # XXX: this is super-inefficient if 'Upgrade' not in headers or headers['Upgrade'].lower( ) != 'websocket': headers['Connection'] = 'close' # write http banner plus headers sockwriter.write(method + b' ' + rawPath + b' ' + proto + b'\r\n') for k, v in headers.items(): sockwriter.write(f'{k}: {v}\r\n'.encode('utf-8')) sockwriter.write(b'\r\n') async def beforeABClose(result): if result == 0: # no response received from client logger.info( f'{connid}-{reqUrl}: route {routeKey} got no result from server' ) writer.write( b'HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\n') await proxy((sockreader, sockwriter, 'sock'), (reader, writer, 'web'), logger=logger, logPrefix=connid, beforeABClose=beforeABClose)
def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) ctx.push() req = ctx.request charset = req.charset session = ctx.session inner_ctx = ctx should_proxy = self.get_logged_in(session) environ['flask_protector_app.verify_login'] = partial(self.verify_login, session=session) environ['flask_protector_app.set_logged_in'] = partial(self.set_logged_in, session) environ['flask_protector_app.get_logged_in'] = partial(self.get_logged_in, session) environ['flask_protector_app.get_logged_in_as'] = partial(self.get_logged_in_as, session) new_environ = environ.copy() if should_proxy: if 'HTTP_COOKIE' in new_environ: # Scrub the environment of any trace of the protector's cookie, # because otherwise the inner app will see it and probably try # to send Set-Cookie headers to refresh the session, effectively undoing # any changes the protector wants to make to it. parsed_cookie = BaseCookie() parsed_cookie.load(environ['HTTP_COOKIE']) # TODO encoding? del parsed_cookie[self.session_cookie_name] stringified_cookie = str(parsed_cookie).partition('Set-Cookie: ')[2] if stringified_cookie: new_environ['HTTP_COOKIE'] = stringified_cookie else: del new_environ['HTTP_COOKIE'] inner_ctx = type(ctx)( self.wrapped_app, environ=new_environ, request=self.wrapped_app.request_class(new_environ) ) error = None try: response = None try: if should_proxy: inner_ctx.push() if not should_proxy: response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise if response is not None: return response(new_environ, start_response) finally: if self.should_ignore_error(error): error = None if should_proxy: result = self.wrapped_app.wsgi_app(new_environ, start_response) inner_ctx.auto_pop(error) return result ctx.auto_pop(error)