Source code for opennode.oms.endpoint.httprest.root

import json
import zope.security.interfaces

from twisted.internet import defer
from twisted.python import log, failure
from twisted.web import resource
from twisted.web.server import NOT_DONE_YET
from zope.component import queryAdapter, getUtility

from opennode.oms.config import get_config
from opennode.oms.endpoint.httprest.base import IHttpRestView, IHttpRestSubViewFactory
from opennode.oms.model.traversal import traverse_path
from opennode.oms.security.checker import proxy_factory
from opennode.oms.security.interaction import new_interaction
from opennode.oms.util import blocking_yield
from opennode.oms.zodb import db


[docs]class EmptyResponse(Exception): pass
[docs]class HttpStatus(Exception): def __init__(self, body=None, *args, **kwargs): super(HttpStatus, self).__init__(*args, **kwargs) self.body = body @property
[docs] def status_code(self): raise NotImplementedError
@property
[docs] def status_description(self): raise NotImplementedError
headers = {}
[docs]class NotFound(HttpStatus): status_code = 404 status_description = "Not Found"
[docs]class NotImplemented(HttpStatus): status_code = 501 status_description = "Not Implemented"
[docs]class AbstractRedirect(HttpStatus): def __init__(self, url, *args, **kwargs): super(AbstractRedirect, self).__init__(*args, **kwargs) self.url = url @property
[docs] def headers(self): return {'Location': self.url}
[docs]class SeeCanonical(AbstractRedirect): status_code = 301 status_description = "Moved Permanently"
[docs]class SeeOther(AbstractRedirect): status_code = 303 status_description = "Moved Temporarily"
[docs]class Unauthorized(HttpStatus): status_code = 401 status_description = "Authorization Required" headers = {'WWW-Authenticate': 'Basic realm=OMS', 'Set-Cookie': 'oms_auth_token=;expires=Wed, 01 Jan 2000 00:00:00 GMT'}
[docs]class Forbidden(HttpStatus): status_code = 403 status_description = "Forbidden"
[docs]class BadRequest(HttpStatus): status_code = 400 status_description = "Bad Request"
[docs]class HttpRestServer(resource.Resource): """Restful HTTP API interface for OMS. Exposes a JSON web service to communicate with OMS. """
[docs] def getChild(self, name, request): """We are the handler for anything below this base url, except what explicitly added in oms.tac.""" return self
def __init__(self, avatar=None): ## Twisted Resource is a not a new style class, so emulating a super-call resource.Resource.__init__(self) self.avatar = avatar self.use_security_proxy = get_config().getboolean('auth', 'security_proxy_rest')
[docs] def render(self, request): deferred = self._render(request) @deferred def on_error(error): log.msg("Error while rendering http %s" % error, system='httprest') return NOT_DONE_YET
@defer.inlineCallbacks def _render(self, request): request.setHeader('Content-type', 'application/json') origin = request.getHeader('Origin') if origin: request.setHeader('Access-Control-Allow-Origin', origin) request.setHeader('Access-Control-Allow-Credentials', 'true') else: request.setHeader('Access-Control-Allow-Origin', '*') request.setHeader('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS, HEAD') request.setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type, Cache-Control, X-Requested-With') ret = None try: ret = yield self.handle_request(request) if ret is EmptyResponse: raise ret except EmptyResponse: pass except HttpStatus as exc: request.setResponseCode(exc.status_code, exc.status_description) for name, value in exc.headers.items(): request.responseHeaders.addRawHeader(name, value) if exc.body: request.write(json.dumps(exc.body)) else: request.write("%s %s\n" % (exc.status_code, exc.status_description)) if exc.message: request.write("%s\n" % exc.message) except Exception: request.setResponseCode(500, "Server Error") request.write("%s %s\n\n" % (500, "Server Error")) log.err(system='httprest') failure.Failure().printTraceback(request) else: # allow views to take full control of output streaming if ret != NOT_DONE_YET: def render(obj): if isinstance(obj, set): return list(obj) # safeguard against dumping sets if hasattr(obj, '__str__'): return str(obj) log.msg("RENDERING ERROR, cannot json serialize %s" % obj, system='httprest') raise TypeError request.write(json.dumps(ret, indent=2, default=render) + '\n') finally: if ret != NOT_DONE_YET: request.finish()
[docs] def check_auth(self, request): from opennode.oms.endpoint.httprest.auth import IHttpRestAuthenticationUtility authentication_utility = getUtility(IHttpRestAuthenticationUtility) credentials = authentication_utility.get_basic_auth_credentials(request) if credentials: blocking_yield(authentication_utility.authenticate(request, credentials, basic_auth=True)) return authentication_utility.generate_token(credentials) else: return authentication_utility.get_token(request)
[docs] def find_view(self, obj, unresolved_path): view = queryAdapter(obj, IHttpRestView, name=unresolved_path[0] if unresolved_path else '') sub_view_factory = queryAdapter(view, IHttpRestSubViewFactory) if sub_view_factory: view = sub_view_factory.resolve(unresolved_path[1:]) if not view: raise NotFound return view
@db.transact
[docs] def handle_request(self, request): """Takes a request, maps it to a domain object and a corresponding IHttpRestView, and returns the rendered output of that view. """ token = self.check_auth(request) oms_root = db.get_root()['oms_root'] objs, unresolved_path = traverse_path(oms_root, request.path[1:]) if not objs and unresolved_path: objs = [oms_root] obj = objs[-1] interaction = self.get_interaction(request, token) request.interaction = interaction if self.use_security_proxy: obj = proxy_factory(obj, interaction) view = self.find_view(obj, unresolved_path) needs_rw_transaction = view.rw_transaction(request) # create a security proxy if we have a secured interaction if interaction: try: view = proxy_factory(view, interaction) except: # XXX: TODO: define a real exception for this proxy creation error # right now we want to ignore security when there are no declared rules # on how to secure a view pass def get_renderer(view, method): try: return getattr(view, method, None) except zope.security.interfaces.Unauthorized: from opennode.oms.endpoint.httprest.auth import IHttpRestAuthenticationUtility if token or not getUtility(IHttpRestAuthenticationUtility).get_basic_auth_credentials(request): raise Forbidden('User does not have permission to access this resource') raise Unauthorized() for method in ('render_' + request.method, 'render'): # hasattr will return false on unauthorized fields renderer = get_renderer(view, method) if renderer: res = renderer(request) if needs_rw_transaction: return res else: return db.RollbackValue(res) raise NotImplementedError("method %s not implemented\n" % request.method)
[docs] def get_interaction(self, request, token): # TODO: we can quickly disable rest auth # if get_config().getboolean('auth', 'enable_anonymous'): # return None from opennode.oms.endpoint.httprest.auth import IHttpRestAuthenticationUtility authentication_utility = getUtility(IHttpRestAuthenticationUtility) try: principal = authentication_utility.get_principal(token) except: # Avoid that changes in format of security token will require every user # to flush the cookies principal = 'oms.anonymous' if principal != 'oms.anonymous': authentication_utility.renew_token(request, token) if request.method == 'OPTIONS': principal = 'oms.rest_options' return new_interaction(principal)

This Page