Skip to content

Commit

Permalink
Added new auth and session support to server
Browse files Browse the repository at this point in the history
  • Loading branch information
Fergal Walsh authored and Fergal Walsh committed Nov 29, 2013
1 parent 1e137ac commit 30408e7
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 12 deletions.
29 changes: 29 additions & 0 deletions pico/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ def decorator(func):
return decorator


def get_request():
""" Returns the wsgi environ dictionary for the current request """
frame = None
try:
frame = [f for f in inspect.stack() if f[3] == 'call'][0]
request = frame[0].f_locals['request']
except Exception:
request = dummy_request
finally:
del frame
return request


def set_dummy_request(request):
""" Set a dummy request dictionary - for use in the console and testing """
dummy_request.clear()
dummy_request.update(request)


class Pico(object):
def __init__(self):
pass
Expand All @@ -65,6 +84,15 @@ class JSONString(str):
def __init__(self, s):
pass


class PicoError(Exception):
def __init__(self, message=''):
Exception.__init__(self, message)
self.response = Response(status="500 " + message, content=message)

def __str__(self):
return repr(self.message)

def convert_keys(obj):
if type(obj) == dict: # convert non string keys to strings
return dict((str(k), convert_keys(obj[k])) for k in obj)
Expand Down Expand Up @@ -131,3 +159,4 @@ def from_json(v, _json_loaders = []):

magic = 'hjksdfjgg;jfgfdggldfgj' # used in the check to see if a module has explicitly imported Pico to make it Picoable
STREAMING = False
dummy_request = {'DUMMY_REQUEST': True}
56 changes: 56 additions & 0 deletions pico/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pico
from pico import PicoError


class NotAuthorizedError(PicoError):
def __init__(self, message=''):
PicoError.__init__(self, message)
self.response.status = "401 Not Authorized"
self.response.set_header("WWW-Authenticate", "Basic")


class InvalidSessionError(PicoError):
def __init__(self, message=''):
PicoError.__init__(self, message)
self.response.status = "440 Invalid Session"


class Bunch:
def __init__(self, **kwds):
self.__dict__.update(kwds)


class object(pico.object):
account_manager = None
__headers__ = {'X-SESSION-ID': ''}

def __init__(self):
super(object, self).__init__()
self.username = None
if type(self.account_manager) == dict:
self.account_manager = Bunch(**self.account_manager)
request = pico.get_request()
if 'HTTP_AUTHORIZATION' in request:
try:
auth_header = request.get('HTTP_AUTHORIZATION')
scheme, data = auth_header.split(None, 1)
assert(scheme == 'Basic')
username, password = data.decode('base64').split(':', 1)
self.user = self._get_user(username, password)
except Exception, e:
raise NotAuthorizedError(str(e))
elif 'HTTP_X_SESSION_ID' in request:
session_id = request.get('HTTP_X_SESSION_ID')
self.user = self._get_session(session_id)
elif 'DUMMY_REQUEST' in request:
pass
else:
raise NotAuthorizedError("No username or password supplied")

def _get_user(self, username, password):
if self.account_manager:
return self.account_manager._get_user(username, password)

def _get_session(self, session_id):
if self.account_manager:
return self.account_manager._get_session(session_id)
36 changes: 24 additions & 12 deletions pico/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,13 @@ def call(params, request):
response.callback = callback
return response

def _load(module_name, params={}):

def _load(module_name, params, environ):
params['_module'] = 'pico.modules'
params['_function'] = 'load'
params['module_name'] = '"%s"'%module_name
return call(params)
params['module_name'] = '"%s"' % module_name
return call(params, environ)


def serve_file(file_path):
response = Response()
Expand Down Expand Up @@ -331,32 +333,40 @@ def generate_exception_report(e, path, params):
return response


def handle_api_v1(path, params):
def _value_summary(value):
s = repr(value)
if len(s) > 100:
s = s[:100] + '...'
return s


def handle_api_v1(path, params, environ):
if '/module/' in path:
module_name = path.split('/')[2]
return _load(module_name, params)
return _load(module_name, params, environ)
elif '/call/' in path:
return call(params, environ)
raise APIError()

def handle_api_v2(path, params):

def handle_api_v2(path, params, environ):
# nice urls:
# /module_name/
# /module_name/function_name/?foo=bar
# /module_name/function_name/foo=bar # not implemented!
# /module_name/class_name/function_name/
parts = [p for p in path.split('/') if p]
if len(parts) == 1:
return _load(parts[0], params)
return _load(parts[0], params, environ)
elif len(parts) == 2:
params['_module'] = parts[0]
params['_function'] = parts[1]
return call(params)
return call(params, environ)
elif len(parts) == 3:
params['_module'] = parts[0]
params['_class'] = parts[1]
params['_function'] = parts[2]
return call(params)
return call(params, environ)
raise APIError(path)

def handle_pico_js(path, params):
Expand Down Expand Up @@ -388,22 +398,24 @@ def wsgi_app(environ, start_response, enable_static=False):
if '/pico/' in path:
path = path.replace('/pico/', '/')
try:
response = handle_api_v1(path, params)
response = handle_api_v1(path, params, environ)
except APIError:
try:
response = handle_pico_js(path, params)
except APIError:
try:
response = handle_api_v2(path, params)
response = handle_api_v2(path, params, environ)
except APIError:
response = not_found_error(path)
elif enable_static:
elif enable_static:
try:
response = static_file_handler(path)
except OSError, e:
response = not_found_error(path)
else:
response = not_found_error(path)
except PicoError, e:
response = e.response
except Exception, e:
response = generate_exception_report(e, path, params)
response.set_header('Access-Control-Allow-Origin', '*')
Expand Down

0 comments on commit 30408e7

Please sign in to comment.