view roundup/xmlrpc.py @ 3973:85cbaa50eba1

xml-rpc security checks and tests across all backends [SF#1907211] also add some leap year tests
author Richard Jones <richard@users.sourceforge.net>
date Fri, 07 Mar 2008 01:11:55 +0000
parents 3c3077582c16
children f7f3068b32a1
line wrap: on
line source

#
# Copyright (C) 2007 Stefan Seefeld
# All rights reserved.
# For license terms see the file COPYING.txt.
#

import base64
import roundup.instance
from roundup import hyperdb
from roundup.cgi.exceptions import *
from roundup.admin import UsageError
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class RoundupRequestHandler(SimpleXMLRPCRequestHandler):
    """A SimpleXMLRPCRequestHandler with support for basic
    HTTP Authentication."""

    def do_POST(self):
        """Extract username and password from authorization header."""

        # Try to extract username and password from HTTP Authentication.
        self.username = None
        self.password = None
        authorization = self.headers.get('authorization', ' ')
        scheme, challenge = authorization.split(' ', 1)

        if scheme.lower() == 'basic':
            decoded = base64.decodestring(challenge)
            self.username, self.password = decoded.split(':')

        SimpleXMLRPCRequestHandler.do_POST(self)

    def _dispatch(self, method, params):
        """Inject username and password into function arguments."""

        # Add username and password to function arguments
        params = [self.username, self.password] + list(params)
        return self.server._dispatch(method, params)


class RoundupRequest:
    """Little helper class to handle common per-request tasks such
    as authentication and login."""

    def __init__(self, tracker, username, password):
        """Open the database for the given tracker, using the given
        username and password."""

        self.tracker = tracker
        self.db = self.tracker.open('admin')
        try:
            self.userid = self.db.user.lookup(username)
        except KeyError: # No such user
            self.db.close()
            raise Unauthorised, 'Invalid user'
        stored = self.db.user.get(self.userid, 'password')
        if stored != password:
            # Wrong password
            self.db.close()
            raise Unauthorised, 'Invalid user'
        self.db.setCurrentUser(username)

    def close(self):
        """Close the database, after committing any changes, if needed."""

        try:
            self.db.commit()
        finally:
            self.db.close()

    def get_class(self, classname):
        """Return the class for the given classname."""

        try:
            return self.db.getclass(classname)
        except KeyError:
            raise UsageError, 'no such class "%s"'%classname

    def props_from_args(self, cl, args):
        """Construct a list of properties from the given arguments,
        and return them after validation."""

        props = {}
        for arg in args:
            if arg.find('=') == -1:
                raise UsageError, 'argument "%s" not propname=value'%arg
            l = arg.split('=')
            if len(l) < 2:
                raise UsageError, 'argument "%s" not propname=value'%arg
            key, value = l[0], '='.join(l[1:])
            if value:
                try:
                    props[key] = hyperdb.rawToHyperdb(self.db, cl, None,
                        key, value)
                except hyperdb.HyperdbValueError, message:
                    raise UsageError, message
            else:
                props[key] = None

        return props


#The server object
class RoundupServer:
    """The RoundupServer provides the interface accessible through
    the Python XMLRPC mapping. All methods take an additional username
    and password argument so each request can be authenticated."""

    def __init__(self, tracker, verbose = False):
        self.tracker = roundup.instance.open(tracker)
        self.verbose = verbose

    def list(self, username, password, classname, propname=None):
        r = RoundupRequest(self.tracker, username, password)
        try:
            cl = r.get_class(classname)
            if not propname:
                propname = cl.labelprop()
            result = [cl.get(itemid, propname)
                for itemid in cl.list()
                     if r.db.security.hasPermission('View', r.userid,
                         classname, propname, itemid)
            ]
        finally:
            r.close()
        return result

    def display(self, username, password, designator, *properties):
        r = RoundupRequest(self.tracker, username, password)
        try:
            classname, itemid = hyperdb.splitDesignator(designator)
            cl = r.get_class(classname)
            props = properties and list(properties) or cl.properties.keys()
            props.sort()
            for p in props:
                if not r.db.security.hasPermission('View', r.userid,
                        classname, p, itemid):
                    raise Unauthorised('Permission to view %s of %s denied'%
                            (p, designator))
            result = [(prop, cl.get(itemid, prop)) for prop in props]
        finally:
            r.close()
        return dict(result)

    def create(self, username, password, classname, *args):
        r = RoundupRequest(self.tracker, username, password)
        try:
            if not r.db.security.hasPermission('Create', r.userid, classname):
                raise Unauthorised('Permission to create %s denied'%classname)

            cl = r.get_class(classname)

            # convert types
            props = r.props_from_args(cl, args)

            # check for the key property
            key = cl.getkey()
            if key and not props.has_key(key):
                raise UsageError, 'you must provide the "%s" property.'%key

            # do the actual create
            try:
                result = cl.create(**props)
            except (TypeError, IndexError, ValueError), message:
                raise UsageError, message
        finally:
            r.close()
        return result

    def set(self, username, password, designator, *args):
        r = RoundupRequest(self.tracker, username, password)
        try:
            classname, itemid = hyperdb.splitDesignator(designator)
            cl = r.get_class(classname)
            props = r.props_from_args(cl, args) # convert types
            for p in props.iterkeys ():
                if not r.db.security.hasPermission('Edit', r.userid,
                        classname, p, itemid):
                    raise Unauthorised('Permission to edit %s of %s denied'%
                        (p, designator))
            try:
                return cl.set(itemid, **props)
            except (TypeError, IndexError, ValueError), message:
                raise UsageError, message
        finally:
            r.close()

# vim: set et sts=4 sw=4 :

Roundup Issue Tracker: http://roundup-tracker.org/