-----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(a)gmail.com
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (GNU/Linux)
Comment: Using GnuPG with SUSE -
http://enigmail.mozdev.org
iD8DBQFGlEi3FRAT5u/mSaMRAqu6AKCT+22XalTgYMLrslOQHeZKh1UwTQCfcc+t
ZxzB90ngEfCKafHiWKO+qp8=
=/5hq
-----END PGP SIGNATURE-----