-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Sorry for totally branching the thread here; I just wanted to add in some insight from a discussion that Simetrical and I had about reform of the access control in MediaWiki.
Firstly, everyone seems to agree that we need to change the behavior of Title::userCan and access control on MediaWiki in general. Along with this there is the general agreement that either the backward-compatibility of our current method should be maintained or that all modules, both in the mw core and extensions, should be updated to work with the new method.
Part of this reform may include Aaron's suggestion--that is, to allow us to determine *if* the user can do something and, if not, *why* he can't in as few invocations as possible and in a manner that reduces the duplication of effort. Mechanisms of doing this include Aaron's suggestion of changing the return type of Title:userCan to an appropriate failure message (this option I find sort of .. gross), using a method to *assert* a user's ability to do something and to throw an exception if he is unable to do (what is generally my preference, but certainly not everyone's cup of tea), or somehow storing data pertaining to why the access request failed that can later be accessed by methods that care. In any case, what needs to be done away with is our current circus of:
1. Method asks if the user can edit something. 2. userCan method cycles through a hundred reasons why he couldn't, finds one, and returns false. 3. Method loops through its (possibly smaller) set of reasons for why the user couldn't do something, finds one, and calls the appropriate OutputPage function. 4. OutputPage function loops through its hundred reasons, finds one, and displays the corresponding message.
The far more pressing issue here, however, and indeed the one that is most important to the developers of extensions and the API and similar frameworks, is the need for a single, reliable, complete, centralized access-checking mechanism. At present we have only basic centralized access-checking, such as Title::userCan, and for more complex cases we are forced to rely upon individual interpretation of what a certain right implies. This is quite dangerous. Take, for instance, Yuri's case of determining whether or not to include the title of a page in a list--it is not set in stone anywhere that User:Joe Blow can or cannot see the title, so how is the API to make this determination? We can mimic what the standard interface does, but what do we do when that changes? Another well-known problem with MediaWiki is that restricting read access is next to impossible, as all extensions and functions are free to use whatever methods they want (or none at all) to determine if a user can view a page--as such, even if userCan says no, Special:Export or action=diff might decide otherwise. It's not the fault of these features nor of their implementors; rather, it is the fault of the core code for making it such a PITA to have to check a whole slew of conditions on their own before doing anything. It's only natural that one or two conditions may be forgotten, and again we see an unnecessary duplication of effort
In my opinion, access-checking is performed best not independent of but rather as part of all accessors--that is, rather than relying upon extensions to check that the user can read or modify a bit of data before doing so, simply don't allow access to the data without first performing checks to verify that the user can access it. By this design, if Special:MySuperCoolSpecialPage calls Title::getPageContent() on a page the user cannot read, he gets an access exception. Implementation of this functionality is quite simple: (pseudocode ...)
/* In User.php */ function assertUserCan( $action, $title ) { if ( !$this->isAllowed( 'read' ) ) throw new MWAccessException( wfMsg( 'errormsgname' ) ); }
/* In Title.php */
function getPageContent() { assertUserCan( 'read', $this ); // .... Hand back the content. }
/* In MySuperExtension */
function execute() { global $wgOut; try { $this->fooBar(): } catch ( MWAccessException $e ) { $wgOut->addHTML( $e->getComment() ); } }
function fooBar() { global $wgOut; $title = Title::newFromText( 'Foobar' ); $wgOut->addWikiText( $title->getPageContent() ): }
Naturally, we can also catch these exceptions in wfRunHooks or elsewhere so as to prevent the writers of extensions from having to fuss with them, although they'll be free to deal with them themselves if they want. It also frees us up from having to use stuff like OutputPage::readOnlyPage() in that the message to use is determined by the assertUserCan method, and it saves us from a *multitude* of invocations--in fact (theoretically of course) we shouldn't have to do *any* access checking outside of that one method; our cost is only having to update accessors to include _one_ invocation of that method.
In any case, this is likely not a final solution, just some food for thought ...
Now to touch upon a few points that Simetrical, Yuri, Andrew, and I discussed (and then I assure you my blabber is coming to an end..). For one, we really need not one, but two userCan methods. One should be in Title and should check a user's access to a *page*. That is, can the user read this page? Can he edit it? Can he move, protect, delete it? The other should find itself in User.php and should take two arguments: an action and another User. This should be used to determine if the user can block, unblock, rename, or whatever else to the provided user. By separating out these two items and providing a mechanism for checking a user's access over another user we provide previously unavailable functionality to webmasters such as the ability to prevent sysops from blocking crats, etc., and are again able to centralize our access control, rather than using our current method of checking "Does this user have the 'block' right?" before blocking, etc.
One last suggestion, one that is likely further down the road if on the road at all, would be a migration or incorporation of user rights from our current internal system ($wgGroupPermissions, etc.) to a dynamic table that associates rights with pages or other entities, or to the simple addition of a couple of fields to the page table. Such a table could have the structure: - page_id (the id of the page) - page_name (the name of the page) - page_namespace (the pagenamespace) - edit_right (the right required to edit this page) - move_right (the right required to move this page) - read_right (the right required to read this page)
Naturally other structures are possible, and likely much, much better, than the above one; however, the basic idea is that the rights required to perform an action on a page are associated *somehow* with that page in the database. The table could be updated through an update script that would simply set the rights for each page according to what would make userCan happy. Naturally, the script would have to be run after *every* change to user groups or rights configuration.
Eventually it would be very nice to have all groups and associated permissions stored in the database itself and modifiable through an onsite UI. Such a mechanism is used by most webware, and this editing of a LocalSettings file is unique, to my knowledge, to MediaWiki and indeed is quite awkward to most users.
Anyway, consider this blabber at an end. Hopefully there are some ideas in here that can be of use.
- -- Daniel Cannon (AmiDaniel)
http://amidaniel.com cannon.danielc@gmail.com