annotate roundup/actions.py @ 5710:0b79bfcb3312

Add support for making an idempotent POST. This allows retrying a POST that was interrupted. It involves creating a post once only (poe) url /rest/data/<class>/@poe/<random_token>. This url acts the same as a post to /rest/data/<class>. However once the @poe url is used, it can't be used for a second POST. To make these changes: 1) Take the body of post_collection into a new post_collection_inner function. Have post_collection call post_collection_inner. 2) Add a handler for POST to rest/data/class/@poe. This will return a unique POE url. By default the url expires after 30 minutes. The POE random token is only good for a specific user and is stored in the session db. 3) Add a handler for POST to rest/data/<class>/@poe/<random token>. The random token generated in 2 is validated for proper class (if token is not generic) and proper user and must not have expired. If everything is valid, call post_collection_inner to process the input and generate the new entry. To make recognition of 2 stable (so it's not confused with rest/data/<:class_name>/<:item_id>), removed @ from Routing::url_to_regex. The current Routing.execute method stops on the first regular expression to match the URL. Since item_id doesn't accept a POST, I was getting 405 bad method sometimes. My guess is the order of the regular expressions is not stable, so sometime I would get the right regexp for /data/<class>/@poe and sometime I would get the one for /data/<class>/<item_id>. By removing the @ from the url_to_regexp, there was no way for the item_id case to match @poe. There are alternate fixes we may need to look at. If a regexp matches but the method does not, return to the regexp matching loop in execute() looking for another match. Only once every possible match has failed should the code return a 405 method failure. Another fix is to implement a more sophisticated mechanism so that @Routing.route("/data/<:class_name>/<:item_id>/<:attr_name>", 'PATCH') has different regexps for matching <:class_name> <:item_id> and <:attr_name>. Currently the regexp specified by url_to_regex is used for every component. Other fixes: Made failure to find any props in props_from_args return an empty dict rather than throwing an unhandled error. Make __init__ for SimulateFieldStorageFromJson handle an empty json doc. Useful for POSTing to rest/data/class/@poe with an empty document. Testing: added testPostPOE to test/rest_common.py that I think covers all the code that was added. Documentation: Add doc to rest.txt in the "Client API" section titled: Safely Re-sending POST". Move existing section "Adding new rest endpoints" in "Client API" to a new second level section called "Programming the REST API". Also a minor change to the simple rest client moving the header setting to continuation lines rather than showing one long line.
author John Rouillard <rouilj@ieee.org>
date Sun, 14 Apr 2019 21:07:11 -0400
parents ed02a1e0aa5d
children 48a1f919f894
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
1 #
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
2 # Copyright (C) 2009 Stefan Seefeld
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
3 # All rights reserved.
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
4 # For license terms see the file COPYING.txt.
5604
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
5 # Actions used in REST and XMLRPC APIs
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
6 #
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
7
5071
a7541077cf12 Remove 'import *' statement from actions.py
John Kristensen <john@jerrykan.com>
parents: 4357
diff changeset
8 from roundup.exceptions import Unauthorised
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
9 from roundup import hyperdb
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
10 from roundup.i18n import _
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
11
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
12 class Action:
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
13 def __init__(self, db, translator):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
14 self.db = db
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
15 self.translator = translator
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
16
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
17 def handle(self, *args):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
18 """Action handler procedure"""
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
19 raise NotImplementedError
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
20
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
21 def execute(self, *args):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
22 """Execute the action specified by this object."""
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
23
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
24 self.permission(*args)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
25 return self.handle(*args)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
26
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
27
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
28 def permission(self, *args):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
29 """Check whether the user has permission to execute this action.
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
30
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
31 If not, raise Unauthorised."""
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
32
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
33 pass
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
34
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
35
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
36 def gettext(self, msgid):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
37 """Return the localized translation of msgid"""
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
38 return self.translator.gettext(msgid)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
39
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
40
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
41 _ = gettext
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
42
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
43
5604
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
44 class PermCheck(Action):
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
45 def permission(self, designator):
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
46
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
47 classname, itemid = hyperdb.splitDesignator(designator)
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
48 perm = self.db.security.hasPermission
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
49
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
50 if not perm('Retire', self.db.getuid(), classname=classname
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
51 , itemid=itemid):
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
52 raise Unauthorised(self._('You do not have permission to retire '
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
53 'or restore the %(classname)s class.')
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
54 %locals())
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
55
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
56 class Retire(PermCheck):
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
57
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
58 def handle(self, designator):
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
59
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
60 classname, itemid = hyperdb.splitDesignator(designator)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
61
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
62 # make sure we don't try to retire admin or anonymous
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
63 if (classname == 'user' and
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
64 self.db.user.get(itemid, 'username') in ('admin', 'anonymous')):
4357
13b3155869e0 Beginnings of a big code cleanup / modernisation to make 2to3 happy
Richard Jones <richard@users.sourceforge.net>
parents: 4125
diff changeset
65 raise ValueError(self._(
13b3155869e0 Beginnings of a big code cleanup / modernisation to make 2to3 happy
Richard Jones <richard@users.sourceforge.net>
parents: 4125
diff changeset
66 'You may not retire the admin or anonymous user'))
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
67
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
68 # do the retire
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
69 self.db.getclass(classname).retire(itemid)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
70 self.db.commit()
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
71
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
72
5604
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
73 class Restore(PermCheck):
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
74
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
75 def handle(self, designator):
4083
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
76
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
77 classname, itemid = hyperdb.splitDesignator(designator)
bbab97f8ffb2 XMLRPC improvements:
Stefan Seefeld <stefan@seefeld.name>
parents:
diff changeset
78
5604
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
79 # do the restore
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
80 self.db.getclass(classname).restore(itemid)
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
81 self.db.commit()
ed02a1e0aa5d Fix actions
Ralf Schlatterbeck <rsc@runtux.com>
parents: 5071
diff changeset
82

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