CherryPy access restrictions with static files - python

I have a CherryPy server that dispenses a few static HTML/JS/etc. files to /foosball, plus some JSON through a REST API to /.
import cherrypy
config = {
'global': {
'server.socket_host': '0.0.0.0',
'server.socket_port': # my port here,
'tools.json_in.on': True
},
'/foosball': {
'tools.staticdir.on': True,
'tools.staticdir.root': '/var/www/html',
'tools.staticdir.dir': 'foosball',
'tools.staticdir.index': 'index.html'
}
}
#cherrypy.popargs('player_id')
class RESTClient_Player(object):
# stuff
class RESTClient_Game(object):
# stuff
class RESTClient:
players = RESTClient_Player()
games = RESTClient_Game()
#cherrypy.expose
def index(self):
http_method = getattr(self, cherrypy.request.method)
return (http_method)()
cherrypy.quickstart(RESTClient(), '/', config)
I also want to keep these pages protected by a basic access restriction scheme, so I've been examining the excellent tutorial CherryPy provides.
Trouble is, the documentation is geared towards authenticating non-static pages, the kind explicitly declared by def statements. I tried and failed to adapt this documentation to the files in /foosball, but without success. /foosball always ends up loading without any authentication request.
What can I add to give static files some access restriction ability?
Thanks!
EDIT: I got pointed towards auth_tool. With the below config block, I was able to lock up the REST API portion with a login screen, but all static files in /foosball are still openly accessible:
def check_login_and_password(login, password):
cherrypy.log(login)
cherrypy.log(password)
return
config = {
'global': {
'server.socket_host': '0.0.0.0',
'server.socket_port': # my port here,
'tools.json_in.on': True,
'tools.sessions.on': True,
'tools.session_auth.on': True,
'tools.session_auth.check_username_and_password': check_login_and_password
},
'/foosball': {
'tools.staticdir.on': True,
'tools.staticdir.root': '/var/www/html',
'tools.staticdir.dir': 'foosball',
'tools.staticdir.index': 'index.html',
'tools.sessions.on': True,
'tools.session_auth.on': True,
'tools.session_auth.check_username_and_password': check_login_and_password
}
}

Instead of using the "staticdir" in your config, you can create a function in your class that will return static files. If you do that, you can wrap authentication around your function.
import cherrypy
from cherrypy.lib.static import serve_file
import os
class Hello(object):
#cherrypy.expose
def index(self):
return "Hello World"
#cherrypy.expose
def static(self, page):
return serve_file(os.path.join(current_dir, 'static', page), content_type='text/html')
if __name__ == '__main__':
current_dir = os.path.dirname(os.path.abspath(__file__))
cherrypy.quickstart(Hello())

Related

Cannot seem able to modify cache value from celery task

Description:
I want to have a cached value (let's call it a flag) to know when a celery task finishes execution.
I have a view for the frontend to poll this flag until it turns to False.
Code:
settings.py:
...
MEMCACHED_URL = os.getenv('MEMCACHED_URL', None) # Cache of devel or production
if MEMCACHED_URL:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': MEMCACHED_URL,
}
}
else:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
api/views.py:
def a_view(request):
# Do some stuff
cache.add(generated_flag_key, True)
tasks.my_celery_task.apply_async([argument_1, ..., generated_flag_key])
# Checking here with cache.get(generated_flag_key), the value is True.
# Do other stuff.
tasks.py:
#shared_task
def my_celery_task(argument_1, ..., flag_cache_key):
# Do stuff
cache.set(flag_cache_key, False) # Checking here with
# cache.get(flag_cache_key),the
# flag_cache_key value is False
views.py:
def get_cached_value(request, cache_key):
value = cache_key.get(cache_key) # This remains True until the cache key
# expires.
Problem:
If I run the task synchronously everything works as expected. When I run the task asynchronously, the cache key stays the same (as expected) and it is correctly passed around through those 3 methods, but the cached value doesn't seem to be updated between the task and the view.
If you run your tasks asynchronously, they are part of different processes which means that because of the LocMemCache backend, the task and the view will not use the same storage (each has its own memory).
Since #Linovia's answer and a dive in Django's documentation, I am now using django-redis as a workaround for my case.
The only thing that needs to change is the CACHES settings (and an active Redis server of course!):
settings.py:
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": 'redis://127.0.0.1:6379/1',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
Now the cache storage is singular.
django-redis is a well-documented library and one can follow the instructions to make it work.

"TypeError: object is not callable" using `cherrypy.dispatch.MethodDispatcher()`

I'm following along with the cherrypy tutorial "Give it a REST", except I wanted to have my cherrypy server start two classes: One to serve some static files, and another one for the RESTful API:
api.py:
import cherrypy
class TestApi(object):
conf = {
'/api/v1/test': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
}
}
exposed = True
def GET(self):
return "Test GET!"
server.py:
import cherrypy
import api
server_conf = {
'server.socket_port': 1313,
}
class Root(object):
conf = {
'/': {
'tools.staticdir.on': True,
'tools.staticdir.dir': "/some/path",
'tools.staticdir.debug': True
}
}
#cherrypy.expose
def index(self):
return "Hello world!"
if __name__ == '__main__':
cherrypy.config.update(server_conf)
cherrypy.tree.mount(Root(), '/', Root.conf)
cherrypy.tree.mount(api.TestApi(), '/api/v1/test',
api.TestApi.conf)
cherrypy.engine.start()
cherrypy.engine.block()
However, when I start the server (python server.py) and do a GET on http://localhost:1313/api/v1/test I get this error:
500 Internal Server Error
The server encountered an unexpected condition which prevented it from
fulfilling the request.
Traceback (most recent call last): File
"/usr/local/lib/python2.7/site-packages/cherrypy/_cprequest.py", line
670, in respond
response.body = self.handler() File "/usr/local/lib/python2.7/site-packages/cherrypy/lib/encoding.py",
line 217, in call
self.body = self.oldhandler(*args, **kwargs) File "/usr/local/lib/python2.7/site-packages/cherrypy/_cpdispatch.py", line
68, in call
raise x TypeError: 'TestApi' object is not callable
I looked up similar questions and came upon
how to use multiple dispatchers in same cherrypy application?, but it's not clear if the answer there is really applicable to me. Any pointers would be appreciated!
Just realized the issue is with the TestApi.conf:
Need to change the path for the config from '/api/v1/test' to '/' in the section below.
class TestApi(object):
conf = {
'/api/v1/test': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
}
}
I guess this is because I already pass in the mount path in server.py so the config path is relative from that point on.

Getting Not Found when using Flask in my Python script for Spotify authorization

I'm trying to upload my Python script to authorize the user for the Spotify iOS SDK. Honestly, I dont know what I'm doing but the documentation is really poor. I'm using Heroku as web server but when I use foreman start I only get this on localhost:5000:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
spotify_token_swap.py looks as following:
import cherrypy
from cherrypy import tools
import simplejson as json
import requests
import os
from flask import Flask
app = Flask(__name__)
# CHANGE these values to your own
k_client_id = "spotify-ios-sdk-beta"
k_client_secret = "ba95c775e4b39b8d60b27bcfced57ba473c10046"
k_client_callback_url = "spotify-ios-sdk-beta://callback"
verbose = True
class SpotifyTokenSwap(object):
#cherrypy.expose
#tools.json_out()
def swap(self, code=None):
params = {
'grant_type': 'authorization_code',
'client_id': k_client_id,
'client_secret': k_client_secret,
'redirect_uri': k_client_callback_url,
'code' : code
}
r = requests.post('https://ws.spotify.com/oauth/token', params)
cherrypy.response.status = r.status_code
if verbose:
print
print code
print r.status_code
print r.text
print
return r.json()
def CORS():
cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
if __name__ == '__main__':
cherrypy.tools.CORS = cherrypy.Tool('before_handler', CORS)
root = SpotifyTokenSwap()
config = {
'global' : {
'server.socket_host' : '0.0.0.0',
'server.socket_port' : 5000,
'server.thread_pool' : 10,
# 'environment' : 'production',
},
'/' : {
'tools.CORS.on' : True,
}
}
cherrypy.quickstart(root, '/', config=config)
and I start the foreman webserver using this in my Procfile:
web: gunicorn spotify_token_swap:app
I'm pretty sure you are pointing to the wrong wsgi application. Pointing to app from the Procfile meant that flask was serving the page. You registered and built everything with cherrypy, and did not include any routes in flask. So the app object had no routes, ie no '/'. So you need to switch to serving the cherrypy app.
Since you're removing the flask app part, you should remove the if __name__ == '__main__': line and change the rest to
config = {
'global' : {
'server.socket_host' : '0.0.0.0',
'server.socket_port' : 5000,
'server.thread_pool' : 10,
# 'environment' : 'production',
},
'/' : {
'tools.CORS.on' : True,
}
}
wsgiapp = cherrypy.Application(SpotifyTokenSwap(), '/', config=config)
And then use this in the ProcFile
web: gunicorn spotify_token_swap:wsgiapp
I'm not used to Foreman or cherrypy, but I think this is what you need to do.
You can use this python service instead:
Download Google App engine here
Install the launcher
Go to ChirsmLarssons GitHub and download the project, it will have everything you need.
In Google app engine launcher, Press add excisting project
Go Google app engines website and create a project, here you will get an app-id
in app.yaml , replace spotifyauth with the app-id
Press deploy
Done, you can now access it on the web at app-id.appspot.com/swap
Before I got this solution, I've spend hours in the jungle of Python and Ruby, Cheers!

How can CherryPy alone be used to serve multiple domains?

I'd like to use a standalone instance of CherryPy to serve several domains from a single server. I'd like each domain to be served from a completely separate CherryPy application, each with its own configuration file.
I played with cherrypy.dispatch.VirtualHost, but it seems like separate configuration files aren't possible.
A similar question (here) suggests that this is quite difficult, but doesn't explain why and might have been due to the fact that no one answered the question.
This CherryPy recipe for multiple apps shows how to load multiple sandboxed apps with separate configuration files, but it looks like they are being served form the same domain.
I can understand that the answer might be, "use CherryPy as a WSGI server behind Nginx or Apache," but I'd rather only deal with CherryPy on this particular server.
In the same repo, there's vhost recipe. However it uses a shared app. I don't see the way to get cherrypy.dispatch.VirtualHost working with separately mounted apps. This is because cherrypy.serving.request.app is set before invocation of the dispatcher. Say you have the following.
hostmap = {
'api.domain.com' : '/app1',
'www.domain.com' : '/app2'
}
cherrypy.tree.mount(App1(), '/app1', appConfig1)
cherrypy.tree.mount(App2(), '/app2', appConfig2)
All what cherrypy.dispatch.VirtualHost does is prepending domain prefix to current url, e.g. requesting http://www.domain.com/foo will result in /app2/foo/ as internal path that is sent to a next dispatcher which is usually cherrypy.dispatch.Dispatcher. However the latter will try to find a page handler using current cherrypy.serving.request.app which is set to empty app because there's nothing in CherryPy tree that corresonded to /foo path. So it will find nothing.
All you need here is to replace prefixing to changing the current app. That is to say changing that line to this.
cherrypy.serving.request.app = cherrypy.tree.apps[prefix]
But because cherrypy.dispatch.VirtualHost is pretty small, you can rewrite in your code easily.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import cherrypy
from cherrypy._cpdispatch import Dispatcher
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 80,
'server.thread_pool' : 8
},
'hostmap' : {
'api.domain.com' : '/app1',
'www.domain.com' : '/app2'
}
}
appConfig1 = {
'/' : {
'tools.json_out.on' : True
}
}
appConfig2 = {
'/' : {
'tools.encode.encoding' : 'utf-8'
}
}
def VirtualHost(nextDispatcher = Dispatcher(), useXForwardedHost = True, **domains):
def dispatch(pathInfo):
request = cherrypy.serving.request
domain = request.headers.get('Host', '')
if useXForwardedHost:
domain = request.headers.get('X-Forwarded-Host', domain)
prefix = domains.get(domain, '')
if prefix:
request.app = cherrypy.tree.apps[prefix]
result = nextDispatcher(pathInfo)
# Touch up staticdir config. See
# https://bitbucket.org/cherrypy/cherrypy/issue/614.
section = request.config.get('tools.staticdir.section')
if section:
section = section[len(prefix):]
request.config['tools.staticdir.section'] = section
return result
return dispatch
class App1:
#cherrypy.expose
def index(self):
return {'bar': 42}
class App2:
#cherrypy.expose
def index(self):
return '<em>foo</em>'
if __name__ == '__main__':
config['/'] = {'request.dispatch': VirtualHost(**config['hostmap'])}
cherrypy.tree.mount(App1(), '/app1', appConfig1)
cherrypy.tree.mount(App2(), '/app2', appConfig2)
cherrypy.quickstart(config = config)

How to logout from a simple web appl. in CherryPy, Python

I am not familiar with CherryPy and Python, but I need to write a very simple web application that performs login ---> do some commands ---> logout. For login I am using the code in the following link:
http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
the application is:
import cherrypy
import os.path
import struct
from auth import AuthController, require, member_of, name_is
class Server(object):
led_power=0
led_switch=1 #Initial LED on
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
auth = AuthController()
#cherrypy.expose
#require()
def index(self, switch='', power=''):
if switch:
self.led_switch = int(switch)
if power:
self.led_power = int(power)
html = open('led.html','r').read()
if self.led_switch:
print "ON"
else:
print "OFF"
if self.led_power:
print "Logout"
cherrypy.session.clear()
return html
index.exposed = True
conf = {
'global' : {
'server.socket_host': '0.0.0.0', #0.0.0.0 or specific IP
'server.socket_port': 8080 #server port
},
'/images': { #images served as static files
'tools.staticdir.on': True,
'tools.staticdir.dir': os.path.abspath('images')
},
'/favicon.ico': { #favorite icon
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.abspath("images/bulb.ico")
}
}
cherrypy.quickstart(Server(), config=conf)
and the html file is:
<html>
<head>
</head>
<body>
<br>
<img src="images/on.png">
<img src="images/off.png">
<p>
<img src="images/Logout.png">
</body>
</html>
with a folder contain three images.
When I run the application I can see the login page on the localhost with username and password fields, then I can reach to the web page which has three button "ON, OFF, Logout".
The problem is I must click the logout button twice to logout, and when I login again and click on any button even the ON or OFF buttons the page is logout and show me the login page again.
I cannot logout in a right way, any help please ?
Thanks
Try running this code. It calls the AuthController().logout() function.
import cherrypy
import os.path
import struct
from auth import AuthController, require, member_of, name_is
class Server(object):
led_power=0
led_switch=1 #Initial LED on
_cp_config = {
'tools.sessions.on': True,
'tools.auth.on': True
}
auth = AuthController()
#cherrypy.expose
#require()
def index(self, switch='', power=''):
if switch:
self.led_switch = int(switch)
if power:
self.led_power = int(power)
html = open('led.html','r').read()
if self.led_switch:
print "ON"
else:
print "OFF"
if self.led_power:
print "Logout"
AuthController().logout()
return html
index.exposed = True
conf = {
'global' : {
'server.socket_host': '0.0.0.0', #0.0.0.0 or specific IP
'server.socket_port': 8080 #server port
},
'/images': { #images served as static files
'tools.staticdir.on': True,
'tools.staticdir.dir': os.path.abspath('images')
},
'/favicon.ico': { #favorite icon
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.abspath("images/bulb.ico")
}
}
cherrypy.quickstart(Server(), config=conf)
Hope this helps.
Andrew

Categories

Resources