Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

- added access restrictions (authorization and throttling) and

  associated decorators

Signed-off-by: Luke Closs <gravatar@5thplane.com>
  • Loading branch information...
commit 1bc0a7d058f827de9fe16be9c332b04a017dc43d 1 parent a90e477
Dan V. safetydank authored lukec committed
Showing with 107 additions and 42 deletions.
  1. +1 −0  Makefile
  2. +54 −0 sensor/www/decorators.py
  3. +52 −42 sensor/www/sensor.py
1  Makefile
View
@@ -18,6 +18,7 @@ install: $(HOOKS_SRC) $(DAEMON) $(LIBS)
# setup sensor web server
install -o www-data -g www-data -m 755 -d $(WWW_ROOT)/sensor
install -o www-data -g www-data -p -m 755 sensor/www/sensor.py $(WWW_ROOT)/sensor
+ install -o www-data -g www-data -p -m 755 sensor/www/decorators.py $(WWW_ROOT)/sensor
# update serial server and startup script
install -o nobody -g dialout -p -m 755 sensor/serialserver.py $(INSTALL_DIR)bin
install sensor/serialserverd /etc/init.d
54 sensor/www/decorators.py
View
@@ -0,0 +1,54 @@
+# danv - some decorators for restricting access to URLs
+
+import web, shelve, datetime
+
+# restricted URL's require this key to be passed in as a GET or
+# POST param.
+SECRET_KEY='7787855982'
+
+# use the @restricted decorator to protect sensitive URLs
+def restricted(view):
+ def _wrapper(*args, **kw):
+ params = web.input(key=None)
+ if params.key != SECRET_KEY:
+ return '!restricted URL - for VHS members only'
+ return view(*args, **kw)
+ return _wrapper
+
+SHELF_PATH = '/tmp/sensor.shelf'
+def throttled(timeout=10, everyone=True):
+ """
+ Throttle access to a view
+
+ timeout -- minimum wait time between responses
+ everyone -- if True, apply throttling to authorized clients as well
+ """
+
+ def _decorator(view):
+ def _wrapper(*args, **kw):
+ params = web.input(key=None)
+
+ if params.key != SECRET_KEY or everyone:
+ # check to see if a recent response is available
+ shelf = shelve.open(SHELF_PATH)
+
+ now = datetime.datetime.now()
+ if shelf.has_key(view.__name__):
+ (timestamp, previous_response) = shelf[view.__name__]
+ if (now - timestamp).seconds <= timeout:
+ return previous_response
+
+ response = view(*args, **kw)
+ shelf[view.__name__] = (now, response)
+ shelf.sync()
+ return response
+
+ else:
+ # authorized clients - no worries!
+ return view(*args, **kw)
+
+ return _wrapper
+
+ return _decorator
+
+
94 sensor/www/sensor.py
View
@@ -8,36 +8,45 @@
import decree # http://bitbucket.org/dantakk/decree
-DOC = """\
-sensor.hackspace.ca - a <a href="http://vancouver.hackspace.ca">VHS</a> project
-
-<a href="/door/state">/door/state</a>
- text/plain response: open|closed (entrance door)
+from decorators import restricted, throttled
-<a href="/door/photo">/door/photo</a>
- image/* response
+TIMEOUT = 30
-<a href="/bathroom/door/state">/bathroom/door/state</a>
- text/plain response: open|closed (bathroom door)
-
-<a href="/temperature/celsius">/temperature/celsius</a>
- text/plain response: (\d+(\.\d*)?) (space temperature in celsius)
+DOC = """\
+sensor.hackspace.ca - a <a href="http://vancouver.hackspace.ca">VHS</a> project
-<a href="/temperature/fahrenheit">/temperature/fahrenheit</a>
- text/plain response: (\d+\.(\d*)?) (space temperature in fahrenheit)
+ <a href="/door/state">/door/state</a>
+ text/plain response: open|closed (entrance door)
+
+ <a href="/door/photo">/door/photo</a>
+ image/jpeg response: a photo of the entrance taken in the last %ds
+
+ <a href="/bathroom/door/state">/bathroom/door/state</a>
+ text/plain response: open|closed (bathroom door)
+
+ <a href="/temperature/celsius">/temperature/celsius</a>
+ text/plain response: (\d+(\.\d*)?) (space temperature in celsius)
+
+ <a href="/temperature/fahrenheit">/temperature/fahrenheit</a>
+ text/plain response: (\d+\.(\d*)?) (space temperature in fahrenheit)
+
+ <a href="/feed/eeml">/feed/eeml</a>
+ text/xml response: an <a href="http://www.eeml.org">EEML</a> XML feed for use with <a href="http://www.pachube.com">pachube</a>
+
+Restricted urls require VHS membership
-<a href="/buzz">/buzz</a>
- text/plain response: (raw buzzer response)
+ <a href="/buzz">/buzz</a>
+ text/plain response: (raw buzzer response)
-<a href="/feed/eeml">/feed/eeml</a>
- text/xml response: an <a href="http://www.eeml.org">EEML</a> XML feed for use with <a href="http://www.pachube.com">pachube</a>
-"""
+ <a href="/door/photo/url">/door/photo/url</a>
+ image/jpeg response: redirects to URL of a new photo of the entrance
+""" % TIMEOUT
SERIAL_HOST_PORT = ('localhost', 9994)
# route urls to handler classes
urls = (
- r'/door/(state|photo)/?', 'Door',
+ r'/door/(state|photo(?:\/url)?)/?', 'Door',
r'/bathroom/door/(state)/?', 'BathroomDoor',
r'/temperature/(celsius|fahrenheit)/?', 'Temperature',
r'/buzz/?', 'Buzz',
@@ -46,20 +55,6 @@
r'.*', 'Static',
)
-# restricted URL's require this key to be passed in as a GET or
-# POST param.
-SECRET_KEY='7787855982'
-
-# use the @restricted decorator to protect sensitive URLs
-def restricted(view):
- def _decorator(*args, **kw):
- params = web.input(key=None)
- if params.key != SECRET_KEY:
- return '!restricted URL - for VHS members only'
-
- return view(*args, **kw)
- return _decorator
-
app = web.application(urls, globals())
def serial_query(query):
@@ -87,7 +82,7 @@ def take_door_photo():
pic_base = config.get('picture_base')
if pic_base:
filename = os.path.join(pic_base, '%s.jpeg' % short_hash)
- os.system('streamer -c /dev/video0 -b 16 -o %s' % filename)
+ os.system('streamer -c /dev/video0 -b 16 -o %s >/dev/null 2>&1' % filename)
short_file = os.path.splitext(filename)[0] + '.jpg'
os.rename(filename, short_file)
pic_uri_base = config.get('picture_uri_base')
@@ -102,7 +97,6 @@ def __init__(self, *args, **kw):
super(Door, self).__init__(*args, **kw)
self.doorname = self.response_cmd = 'door'
- @property
def door_state(self):
response = serial_query('%s state' % self.doorname).strip()
if response == '%s closed' % self.response_cmd:
@@ -112,15 +106,31 @@ def door_state(self):
else:
return '!unknown response <%s>' % response
+ @throttled(timeout=TIMEOUT)
+ def door_photo(self):
+ door_photo_uri, door_photo_path = take_door_photo()
+ if door_photo_path:
+ web.header('Content-Type', 'image/jpeg')
+ web.header('X-Vhs-URL', str(door_photo_uri))
+ return(file(door_photo_path).read())
+
+ return '!door photo not taken - check config'
+
+ @restricted
+ def door_photo_url(self):
+ door_photo_uri, door_photo_path = take_door_photo()
+ if door_photo_uri:
+ raise web.seeother(door_photo_uri)
+
+ return '!door photo not taken - check config'
+
def GET(self, query):
if query == 'state':
- return self.door_state
+ return self.door_state()
elif query == 'photo':
- door_photo_uri, door_photo_path = take_door_photo()
- if door_photo_uri:
- raise web.seeother(door_photo_uri)
- return '!door photo not taken - check config'
- # return str(door_photo_uri)
+ return self.door_photo()
+ elif query == 'photo/url':
+ return self.door_photo_url()
class BathroomDoor(Door):
def __init__(self, *args, **kw):
Please sign in to comment.
Something went wrong with that request. Please try again.