1.1 --- a/MoinMessage.py Tue Dec 17 14:11:38 2013 +0100
1.2 +++ b/MoinMessage.py Sat Dec 21 01:48:54 2013 +0100
1.3 @@ -27,6 +27,9 @@
1.4 def to_store(message):
1.5 return message.get("Update-Action") == "store"
1.6
1.7 +def get_update_action(message):
1.8 + return message.get("Update-Action", "update")
1.9 +
1.10 class Message:
1.11
1.12 "An update message."
2.1 --- a/MoinMessageSupport.py Tue Dec 17 14:11:38 2013 +0100
2.2 +++ b/MoinMessageSupport.py Sat Dec 21 01:48:54 2013 +0100
2.3 @@ -13,6 +13,7 @@
2.4 from MoinSupport import getHeader, getMetadata, getWikiDict, writeHeaders, \
2.5 parseDictEntry
2.6 from ItemSupport import ItemStore
2.7 +from TokenSupport import getIdentifiers
2.8 from MoinMessage import GPG, Message, MoinMessageError, \
2.9 MoinMessageMissingPart, MoinMessageBadContent, \
2.10 is_signed, is_encrypted, getContentAndSignature
2.11 @@ -228,6 +229,32 @@
2.12
2.13 return homedir
2.14
2.15 + def can_perform_action(self, action):
2.16 +
2.17 + """
2.18 + Determine whether the user in the request has the necessary privileges
2.19 + to change the current page using a message requesting the given
2.20 + 'action'.
2.21 + """
2.22 +
2.23 + for identifier in get_update_actions_for_user(self.request):
2.24 +
2.25 + # Expect "action:pagename", rejecting ill-formed identifiers.
2.26 +
2.27 + details = identifier.split(":", 1)
2.28 + if len(details) != 2:
2.29 + continue
2.30 +
2.31 + # If the action and page name match, return success.
2.32 +
2.33 + permitted, pagename = details
2.34 + if permitted.lower() == action.lower() and pagename == self.page.page_name:
2.35 + return True
2.36 +
2.37 + return False
2.38 +
2.39 +# More specific errors.
2.40 +
2.41 class MoinMessageRecipientError(MoinMessageError):
2.42 pass
2.43
2.44 @@ -240,6 +267,8 @@
2.45 class MoinMessageBadRecipient(MoinMessageRecipientError):
2.46 pass
2.47
2.48 +# Utility functions.
2.49 +
2.50 def get_homedir(request):
2.51
2.52 "Locate the GPG home directory."
2.53 @@ -302,20 +331,42 @@
2.54 'fingerprint' or None if no correspondence is present in the mapping page.
2.55 """
2.56
2.57 + # Since this function must be able to work before any user has been
2.58 + # identified, the wikidict operation uses superuser privileges.
2.59 +
2.60 gpg_users = getWikiDict(
2.61 getattr(request.cfg, "moinmessage_gpg_users_page", "MoinMessageUserDict"),
2.62 request,
2.63 - superuser=True # disable user test because we have no user yet
2.64 + superuser=True
2.65 )
2.66
2.67 - # With a user mapping and a fingerprint corresponding to a known
2.68 - # user, temporarily switch user in order to make the edit.
2.69 -
2.70 if gpg_users and gpg_users.has_key(fingerprint):
2.71 return gpg_users[fingerprint]
2.72 else:
2.73 return None
2.74
2.75 +def get_update_actions_for_user(request):
2.76 +
2.77 + """
2.78 + For the user associated with the 'request', return the permitted actions for
2.79 + the user in the form of
2.80 + """
2.81 +
2.82 + if not request.user or not request.user.valid:
2.83 + return []
2.84 +
2.85 + actions = getWikiDict(
2.86 + getattr(request.cfg, "moinmessage_user_actions_page", "MoinMessageUserActionsDict"),
2.87 + request
2.88 + )
2.89 +
2.90 + username = request.user.name
2.91 +
2.92 + if actions and actions.has_key(username):
2.93 + return getIdentifiers(actions[username])
2.94 + else:
2.95 + return []
2.96 +
2.97 def get_recipient_details(request, recipient, main=False, fetching=False):
2.98
2.99 """
3.1 --- a/README.txt Tue Dec 17 14:11:38 2013 +0100
3.2 +++ b/README.txt Sat Dec 21 01:48:54 2013 +0100
3.3 @@ -175,6 +175,38 @@
3.4 added to this mapping and specify the same relaying user; there is no
3.5 restriction on each fingerprint needing to map to a different user.
3.6
3.7 +The Username-to-Actions Mapping
3.8 +-------------------------------
3.9 +
3.10 +Each user may have a set of permitted actions defined for them so that they
3.11 +may perform these actions by sending an incoming message to the wiki. This
3.12 +mapping is typically defined by the MoinMessageUserActionsDict page as a
3.13 +WikiDict having the following general format:
3.14 +
3.15 + username:: permitted-action ...
3.16 +
3.17 +To add content to a page, an entry of the following form would be used:
3.18 +
3.19 + username:: Update:SomePage
3.20 +
3.21 +Similarly, to allow an incoming message to replace a page's content, the
3.22 +following would be used:
3.23 +
3.24 + username:: Replace:SomePage
3.25 +
3.26 +And to be able to add messages to a page's message store, the following would
3.27 +be used:
3.28 +
3.29 + username:: Store:SomePage
3.30 +
3.31 +Multiple actions can be given in a space-separated list, with shell-like
3.32 +quoting used for names containing spaces (and quote characters). For example:
3.33 +
3.34 + username:: Store:"Some user's special page"
3.35 +
3.36 +Without an entry in this mapping, messages may not perform content
3.37 +modification or storage actions in the wiki.
3.38 +
3.39 The Username-to-Signing-Key Mapping
3.40 -----------------------------------
3.41
4.1 --- a/actions/PostMessage.py Tue Dec 17 14:11:38 2013 +0100
4.2 +++ b/actions/PostMessage.py Sat Dec 21 01:48:54 2013 +0100
4.3 @@ -9,7 +9,7 @@
4.4 from MoinMoin.Page import Page
4.5 from MoinMoin.PageEditor import PageEditor
4.6 from MoinSupport import getMetadata, writeHeaders
4.7 -from MoinMessage import is_collection, to_replace, to_store
4.8 +from MoinMessage import is_collection, to_replace, to_store, get_update_action
4.9 from MoinMessageSupport import MoinMessageAction
4.10
4.11 Dependencies = ['pages']
4.12 @@ -26,22 +26,28 @@
4.13
4.14 # Handle each update.
4.15
4.16 + all_successful = True
4.17 +
4.18 for update in message.updates:
4.19
4.20 # Handle a single part.
4.21
4.22 if not is_collection(update):
4.23 - self.handle_message_parts([update], update)
4.24 + all_successful = all_successful and self.handle_message_parts([update], update)
4.25
4.26 # Or a collection of alternative representations for a single
4.27 # update.
4.28
4.29 else:
4.30 - self.handle_message_parts(update.get_payload(), update)
4.31 + all_successful = all_successful and self.handle_message_parts(update.get_payload(), update)
4.32
4.33 # Default output.
4.34
4.35 - writeHeaders(request, "text/plain", getMetadata(self.page), "204 No Content")
4.36 + writeHeaders(request, "text/plain", getMetadata(self.page), "200 OK")
4.37 + if all_successful:
4.38 + request.write("All updates were successful.")
4.39 + else:
4.40 + request.write("Some updates were unsuccessful.")
4.41
4.42 def handle_message_parts(self, parts, update):
4.43
4.44 @@ -53,6 +59,13 @@
4.45
4.46 request = self.request
4.47
4.48 + # Test for privileges to change the page or message store.
4.49 +
4.50 + update_action = get_update_action(update)
4.51 +
4.52 + if not self.can_perform_action(update_action):
4.53 + return False
4.54 +
4.55 # Handle the different update actions.
4.56 # Update a message store for the page.
4.57
4.58 @@ -95,6 +108,8 @@
4.59
4.60 self.page = Page(request, self.pagename)
4.61
4.62 + return True
4.63 +
4.64 # Action function.
4.65
4.66 def execute(pagename, request):