view doc/xmlrpc.txt @ 8411:ef1ea918b07a reauth-confirm_id

feat(security): Add user confirmation/reauth for sensitive changes Auditors can raise Reauth(reason) exception to require the user to enter a token (e.g. account password) to verify the user is performing the change. Naming is subject to change. actions.py: New ReauthAction class handler and verifyPassword() method for overriding if needed. client.py: Handle Reauth exception by calling Client:reauth() method. Default client:reauth method. Add 'reauth' action declaration. exceptions.py: Define and document Reauth exception as a subclass of RoundupCGIException. templating.py: Define method utils.embed_form_fields(). The original form making a change to the database has a lot of form fields. These need to be resubmitted to Roundup as part of the form submission that verifies the user's password. This method turns all non file form fields into type=hidden inputs. It escapes the names and values to prevent XSS. For file form fields, it base64 encodes the contents and puts them in hidden pre blocks. The pre blocks have data attributes for the filename, filetype and the original field name. (Note the original field name is not used.) This stops the file content data (maybe binary e.g. jpegs) from breaking the html page. The reauth template runs JavaScript that turns the encoded data inside the pre tags back into a file. Then it adds a multiple file input control to the page and attaches all the files to it. This file input is submitted with the rest of the fields. _generic.reauth.html (multiple tracker templates): Generates a form with id=reauth_form to: display any message from the Reauth exception to the user (e.g. why user is asked to auth). get the user's password submit the form embed all the form data that triggered the reauth recreate any file data that was submitted as part of the form and generate a new file input to push the data to the back end It has the JavaScript routine (as an IIFE) that regenerates a file input without user intervention. All the TAL based tracker templates use the same form. There is also one for the jinja2 template. The JavaScript for both is the same. reference.txt: document embed_form_fields utility method. upgrading.txt: initial upgrading docs. TODO: Finalize naming. I am leaning toward ConfirmID rather than Reauth. Still looking for a standard name for this workflow. Externalize the javascript in _generic.reauth.html to a seperate file and use utils.readfile() to embed it or change the script to load it from a @@file url. Clean up upgrading.txt with just steps to implement and less feature detail/internals. Document internals/troubleshooting in reference.txt. Add tests using live server.
author John Rouillard <rouilj@ieee.org>
date Mon, 11 Aug 2025 14:01:12 -0400
parents 57325fea9982
children
line wrap: on
line source

.. meta::
    :description:
        Documentation on the XMLRPC interface to the Roundup Issue
	Tracker. Includes sample clients.

.. index:: triple: api; xml; remote procedure call
   pair: api; xmlrpc

=========================
XML-RPC access to Roundup
=========================

.. contents::
   :local:

Introduction
============

Version 1.4 of Roundup includes an XML-RPC frontend for remote access. The
XML-RPC interface allows a limited subset of commands similar to those found in
local `roundup-admin` tool.

By default XML-RPC is accessible from ``/xmlrpc`` endpoint:

    http://username:password@localhost:8000/xmlrpc

For demo tracker the URL would be:

    http://localhost:8917/demo/xmlrpc

Enabling XML-RPC
================
There are two ways to run the XML-RPC interface:

  through roundup itself

  stand alone roundup-xmlrpc-server


Through Roundup
---------------

The XML-RPC service is available from the roundup HTTP server under
/xmlrpc.

To enable this set ``enable_xmlrpc`` to ``yes`` in the ``[web]``
section of the ``config.ini`` file in your tracker.

Each user that needs access must include the "Xmlrpc Access" role. To
add this new permission to the "User" role you should change your
schema.py to add::

    db.security.addPermissionToRole('User', 'Xmlrpc Access')

This is usually included near where other permissions like "Web Access"
or "Email Access" are assigned.

Standalone roundup-xmlrpc-server
--------------------------------

Using Roundup to access the xmlrpc interface is preferred. Roundup
provides better control over who can use the interface.

The Roundup XML-RPC standalone server must be started before remote
clients can access the tracker via XML-RPC. ``roundup-xmlrpc-server``
is installed in the scripts directory alongside ``roundup-server`` and
``roundup-admin``. When invoked, the location of the tracker instance
must be specified.

	roundup-xmlrpc-server -i ``/path/to/tracker``

The default port is ``8000``. An alternative port can be specified with the
``--port`` switch.

Security Consideration
======================

Both the standalone and embedded roundup XML endpoints used the
default python XML parser. This parser is know to have security
issues. For details see: https://pypi.org/project/defusedxml/.
You may wish to use the rest interface which doesn't have the same
issues. If you install defusedxml, it will be automatically used to add
some additional protection.

.. caution::

   The current standalone ``roundup-xmlrpc-server`` implementation
   does not support SSL. This means that usernames and passwords will
   be passed in cleartext unless the server is proxied behind
   another server (such as Apache or lighttpd) that provides SSL.

Rate Limiting Failed Logins
---------------------------

See the `rest documentation
<rest.html#rate-limiting-api-failed-logins>`_ for rate limiting failed
logins on the API. There is no login rate limiting for the standalone
roundup-xmlrpc-server. Login rate limiting is only for the ``/xmlrpc``
endpoint when the Roundup server is used.

The XML-RPC uses the same method as the REST API.
Rate limiting is shared between the XMLRPC and REST APIs.

Client API
==========
The server currently implements seven methods/commands. Each method
requires that the user provide a username and password in the HTTP
authorization header in order to authenticate the request against the
tracker.

.. table::
   :class: valign-top

   ======= ===================================================================
   Command  Description
   ======= ===================================================================
   schema  Fetch tracker schema.

   list    arguments: *classname, [property_name]*

	   List all elements of a given ``classname``. If ``property_name``
	   is specified, that is the property that will be displayed for each
	   element. If ``property_name`` is not specified the default label
	   property will be used.

   display arguments: *designator, [property_1, ..., property_N]*

	   Display a single item in the tracker as specified by ``designator``
	   (e.g. issue20 or user5). The default is to display all properties
	   for the item. Alternatively, a list of properties to display can
	   be specified.

   create  arguments: *classname, arg_1 ... arg_N*

	   Create a new instance of ``classname`` with ``arg_1`` through
	   ``arg_N`` as the values of the new instance. The arguments are
	   name=value pairs (e.g. ``status='3'``).

   set     arguments: *designator, arg_1 ... arg_N*

	   Set the values of an existing item in the tracker as specified by
	   ``designator``. The new values are specified in ``arg_1`` through
	   ``arg_N``. The arguments are name=value pairs (e.g. ``status='3'``).

   lookup  arguments: *classname, key_value*

	   looks up the key_value for the given class. The class needs to
	   have a key and the user needs search permission on the key
	   attribute and id for the given classname.

   filter  arguments: *classname, list or None, attributes*

	   ``list`` is a list of ids to filter. It can be set to None to
	   run filter over all values (requires ``allow_none=True`` when
	   instantiating the ServerProxy). The ``attributes`` are given 
	   as a dictionary of name value pairs to search for. See also
	   :ref:`query-tracker`.
   ======= ===================================================================

Sample Python Client
====================

This client will work if you turn off the x-requested-with header and
the only CSRF header check you require is the HTTP host header::

        >>> import xmlrpclib
        >>> roundup_server = xmlrpclib.ServerProxy('http://admin:admin@localhost:8917/demo/xmlrpc', allow_none=True)
        >>> roundup_server.schema()
        {'user': [['username', '<roundup.hyperdb.String>'], ...], 'issue': [...]}
        >>> roundup_server.list('user')
        ['admin', 'anonymous', 'demo']
        >>> roundup_server.list('issue', 'id')
        ['1']
        >>> roundup_server.display('issue1')
        {'assignedto' : None, 'files' : [], 'title' = 'yes, ..... }
        >>> roundup_server.display('issue1', 'priority', 'status')
        {'priority' : '1', 'status' : '2'}
        >>> roundup_server.set('issue1', 'status=3')
        >>> roundup_server.display('issue1', 'status')
        {'status' : '3' }
        >>> roundup_server.create('issue', "title='another bug'", "status=2")
        '2'
        >>> roundup_server.filter('user',None,{'username':'adm'})
        ['1']
        >>> roundup_server.filter('user',['1','2'],{'username':'adm'})
        ['1']
        >>> roundup_server.filter('user',['2'],{'username':'adm'})
        []
        >>> roundup_server.filter('user',[],{'username':'adm'})
        []
        >>> roundup_server.lookup('user','admin')
        '1'

Advanced Python Client Adding anti-csrf Headers
===============================================

The one below adds Referer and X-Requested-With headers so it can pass
stronger CSRF detection methods. It also generates a fault message
from the server and reports it. Note if you are using http rather than
https, replace xmlrpclib.SafeTransport with xmlrpclib.Transport::

    try:
        from xmlrpc import client as xmlrpclib  # python 3
    except ImportError:
        import xmlrpclib  # python 2

    hostname="localhost"
    path="/demo"
    user_pw="admin:admin"

    class SpecialTransport(xmlrpclib.SafeTransport):

        def send_content(self, connection, request_body):

            connection.putheader("Referer", "https://%s%s/"%(hostname, path))
            connection.putheader("Origin", "https://%s"%hostname)
            connection.putheader("X-Requested-With", "XMLHttpRequest")

            connection.putheader("Content-Type", "text/xml")    
            connection.putheader("Content-Length", str(len(request_body)))
            connection.endheaders()
            if request_body:
                connection.send(request_body)

    roundup_server = xmlrpclib.ServerProxy(
        'https://%s@%s%s/xmlrpc'%(user_pw,hostname,path),
        transport=SpecialTransport(),
        verbose=False,
        allow_none=True)

    print(roundup_server.schema())
    print(roundup_server.display('user2', 'username'))
    print(roundup_server.display('issue1', 'status'))
    print(roundup_server.filter('user',['1','2','3'],{'username':'demo'}))

    # this will fail with a fault
    try:
        print(roundup_server.filter('usr',['0','2','3'],{'username':'demo'}))
    except Exception as msg:
        print(msg)

modify this script replacing the hostname, path and user_pw with those
for your tracker.

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