changed git call from https to git readonly
[atutor.git] / mods / wiki / plugins / auth / auth_perm_unix.php
1 <?php
2
3 /*
4    This ewiki_auth() permission plugin provides UNIX filesystem like
5    file access rights (chmod) to ewiki pages. It should be used together
6    with 'userdb_userregistry' and/or 'userdb_systempasswd', and
7    additionally uses the _SYSTEM page "system/groups" to map users into
8    groups.
9    While this plugin gives rich control over page access permissions
10    ("edit" and "view" rights), it fails greatly on controling other ewiki
11    page actions ("info" and "links" for example). So this plugin tries
12    to map other page actions down to the basic UNIX read and write rights
13    (which is configurable, see below).
14
15    To create the "system/groups" page, you can call a URL ending in
16    "...?id=edit/system/groups" initially. After it is first saved, it will
17    become a _SYSTEM page, and only the admin (ring level 0) can further
18    edit it. The format of that "page" is as follows:
19      # comment
20      groupname:*:user1,user2,...
21      group2:*: user3, ...
22      alsoallowed:*: user4; user5, user6 + user7 | user8 ...
23    Note, that the asterisk is mandatory here (don't use the "x" as in
24    a Unix systems /etc/groups). As user name delimiters you may use spaces,
25    commas, semicolons and a few others (don't do this within /etc/groups!)
26
27    About the UNIX access rights:
28     - The "rwx" refers to the access permissions, where "r" stands for read,
29       "w" for write, and "x" for executable.
30     - There exists such an access right triple for the "owner" of a page,
31       one for the member "groups", and a third for anybody else ("world").
32     - access rights are stored in single bits, and again in the three groups:
33        ... | unused (11-9) | owner (8-6) | group (5-3) | world (2-0) |
34            | [0] [0] [0]   | [r] [w] [x] | [r] [w] [x] | [r] [w] [x] |
35       "owner"=="user", and "world"=="other",
36       the available 15 bits here are referred to as "BBBAAAuuugggooo"
37       (Please also keep in mind, that we here often note the combined value
38       in octals.)
39
40    Things worth to mention about how this plugin works:
41     - Every page can have just one "owner", it however does not require (but
42       for setting the access rights).
43     - But a page can be given to zero, one or multiple "groups" (to be
44       separated by spaces, commas or semicolon when entered on the edit
45       screen).
46     - Only the owner can add groups.
47     - Users are not required to be member of any group.
48     - Ring permission levels are still in use with this plugin. You for
49       example should have at least one administrator (ring 0) user defined
50       in 'system/passwd' or 'system/UserRegistry' (or any other userdb).
51     - Administrators (ring 0) are as usual unrestricted in what they do.
52     - Moderators (ring level 1) always have read/write permission too (but
53       cannot change a pages access rights if they don't own it).
54     - To disallow guest users to change pages, you would want to set
55       EWIKI_PERM_UNIX_UMASK to 0002 - or even 0022 to disallow even group
56       members to edit it (this setting only is for initial defaults).
57     - The owner of a page may edit (not view!) it, even if access rights
58       were in fact insufficient to do that (they must however be reenabled
59       before saving can succeed).
60     - Only moderators can change access bits (12-9), and only superusers
61       can tweak (15-13) (not shown, not used currently).
62     - One must LOGIN BEFORE a page can be occupied. It was possible to add
63       auto-login for page ownership requests; but this would fail for badly
64       written auth_method plugins (only auth_method_http currently would
65       work).
66     - A page owner may chown the page to any other user.
67     - The initial page owner (any account allowed) can be set by logged-in
68       users only, unless _PERM_USERSET_FREE is enabled.
69     - Virtual pages (page plugins) cannot have access rights assigned,
70       unless you create faked database entries with admin/page_searchcache.
71       But editing the access permissions then also required temporarily(!)
72       enabling the edit/ function for the superuser with higher priority:
73       $ewiki_plugins["action_always"]["edit"]="ewiki_page_edit";
74     - The "rwx" access values are combined additive (owner,group,world)
75       and then compared against the minimum rights needed to perform a
76       certain ewiki $action - this way you can map "r", "w" and "x" to
77       "view/", "info/" and "edit/" actions - see ["perm_rights_actions"]
78       in $ewiki_config[].
79       "Additive combination" means merging the effective rights ("uuugggooo")
80       into a 3 bit integer. The "BBBAAA" bits are doubled into bits 8-3, so
81       "BBBAAABBBAAAeee" is available for that comparison - but you'll often
82       just want to check the last three bits.
83     - initial page access rights are (0777 minus _UMASK)
84     - Pages that were already in the database will suffer from virtual 
85       initial access rights (0777 minus _UMASK);
86     - The _UMASK value is denoted in 'octals' (numbering system based upon
87       eight) - which in PHP are differentiated from plain decimals by the
88       preceding zero. Beware, that you should not enclose these numbers in
89       quotes (especially when defining() the _UMASK constant); else it won't
90       work.
91     - Also don't get disturbed by the info/ page showing the "rights" value
92       from the {meta} field as decimal value (instead of octals).
93
94    To use this you'll also need:
95     - plugins/auth/auth_method_http (or _form) plugin
96     - plugins/auth/userdb_* (your choice)
97     - EWIKI_PROTECTED_MODE enabled
98
99    You should not forget that the access rights implemented by this auth
100    plugin don't have anything to do with the ewiki page flags. The database
101    entries {flags} are independent from these and can only be set by the
102    superuser (via external tools or admin/control plugins). That is why
103    _DB_F_READ and _DB_F_WRITE still have precedence over all the _perm_unix
104    page attributes (and thus could be used for exceptions).
105
106    This plugin again proves that ewiki is far ahead of Hurd and coWiki  ;-)
107
108
109 TODO
110 - add strings, error messages, translations
111 - make a simplified variant (coWiki like)
112 */
113
114
115 #-- settings
116 define("EWIKI_USERDB_SYSTEMGROUPS", "system/groups");
117 define("EWIKI_PERM_UNIX_UMASK", 0000);  //access rights to strip for new pages
118 #define("EWIKI_PERM_UNIX_SHAREHOLDERS", 1);  //group members may revoke rights
119 define("EWIKI_PERM_USERSET_FREE", 0);  //anybody could set the initial owner
120 define("EWIKI_USERDB_GROUPDELIMS", ",;:|+*\t");
121
122
123 #-- interface
124 $ewiki_plugins["auth_perm"][] = "ewiki_auth_perm_unix";
125 $ewiki_plugins["edit_form_append"][] = "ewiki_edit_form_append_unix_access_rights";
126 $ewiki_plugins["edit_save"][] = "ewiki_edit_save_unix_access_rights";
127
128
129 #-- text/translations
130 $ewiki_t["de"]["access rights"] = "Zugriffsrechte";
131 $ewiki_t["de"]["owner"] = "Besitzer";
132 $ewiki_t["de"]["groups"] = "Gruppen";
133 $ewiki_t["de"]["world"] = "Welt";
134
135
136
137 #-- association of (string)$ewikiaction => (bitmask)$UnixAccessRights
138 $ewiki_config["perm_rights_actions"] = array(
139
140    #-- actions
141    "view" => 4,   #=read
142    "edit" => 2,   #=write
143    "info" => 4,   #=read
144    "links" => 4,  #=read
145    "search" => 1, #=executable
146    "nop" => 0,    #=always yields ok
147
148    #-- pages
149    "SecretPage" => 16+8,  #=special flags (can't be set currently)
150
151    #-- fallback for any other combination:
152    "*" => 4+1,    #=read+executable
153 );
154
155 #-- also always allowed for ring level 1 users:
156 $ewiki_config["moderators_may_do"] = array(
157    "edit",
158    "view", "info", "links", "search",
159    "control",
160 );
161
162
163 #-- implementation
164 function ewiki_auth_perm_unix($id, &$data, $action, $ring=NULL, $force=0) {
165
166    global $ewiki_plugins, $ewiki_config, $ewiki_auth_user, $ewiki_author, $ewiki_ring;
167
168    #-- eventually refetch page $data
169    if (!$data || (count($data)<5)) {
170       $data = ewiki_db::GET($id);
171    }
172
173    #-- pages access rights
174    ($p_rights = $data["meta"]["rights"]) and isset($p_rights)
175    or ($p_rights = 0777 ^ EWIKI_PERM_UNIX_UMASK);
176
177    #-- state vars
178    $effective_rights = 00;   // owner/groups/world rights ORed together
179    $effective_rights |= ($p_rights >> 9);   // add high/special bits
180
181    #-- take "world" rights also in account
182    $effective_rights |= ($p_rights & 0x07);
183
184    #-- user login (???)
185    # (already done)
186
187    #-- check for authenticated user
188    if (($user = $ewiki_auth_user) || ($user = $ewiki_author)) {
189
190       #-- if user owns the current page
191       $p_owner = $data["meta"]["owner"];
192       if ($p_owner==$user) {
193          if ($action=="edit") {
194             #-- allow to change any permissions on edit page,
195             #   this may still require to load '...?id=edit/CurrentPage' by hand
196             $effective_rights |= 2;
197          }
198          else {
199             $effective_rights |= ($p_rights >> 6) & 0x07;
200          }
201       }
202
203       #-- if user is in one of the mentioned groups
204       if (ewiki_auth_user_in_groups_str($user, $data["meta"]["groups"])) {
205          $effective_rights |= ($p_rights >> 3) & 0x07;
206       }
207    }
208
209    #-- compare against required permissions for current action or/and page
210    ($requ = $ewiki_config["perm_rights_actions"]["$action/$page"]) or
211    ($requ = $ewiki_config["perm_rights_actions"][$page]) or
212    ($requ = $ewiki_config["perm_rights_actions"][$action]) or
213    ($requ = $ewiki_config["perm_rights_actions"]["*"]) or
214    ($requ = 0x0);
215    $goal = (($effective_rights & $requ) == $requ);
216
217 #$ps=base_convert($p_rights, 10, 2);
218 #echo "eff=$effective_rights, requ=$requ, p_r=$ps<br />\n";
219
220    #-- advanced comparasions could be added here(?)
221    /*
222       ...
223    */
224
225    #-- ring permission levels are still here
226    $goal = $goal
227         || isset($ewiki_ring) && ($ewiki_ring == 0) // superuser can always do everything
228         || ($ewiki_ring == 1) && (in_array($action, $ewiki_config["moderators_may_do"]));
229
230    #-- we also take the ring level into account, if one is required
231    if (isset($ring) && is_int($ring)) {
232       $goal = $goal && ($ewiki_ring <= $ring);
233    }
234
235    return($goal);
236 }
237
238
239
240
241 /*
242    The following function allows to change access rights via the edit/
243    page 'interface' - so using the mysql client and phpserialize()
244    is not required
245 */
246
247 #-- print page access rights below textarea on edit page
248 function ewiki_edit_form_append_unix_access_rights($id, &$data, $action) {
249
250    global $ewiki_ring;
251 $ewiki_ring=0;
252
253    #-- get meta data
254    $owner = htmlentities($data["meta"]["owner"]);
255    $groups = htmlentities($data["meta"]["groups"]);
256    ($rights = $data["meta"]["rights"]) and isset($rights)
257    or ($rights = 0777 ^ EWIKI_PERM_UNIX_UMASK);
258
259    #-- build checkboxes
260    $rs = array("", "", "", "", "");
261    for ($n=15; $n>=0; $n--) {
262       $i = (int) ($n/3);
263       $checked = (($rights >> $n) & 0x1) ? ' checked="checked"' : "";
264       $disabled = (($i>=3) && ($ewiki_ring>=2) || ($i>=4) && ($ewiki_ring>=1)) ? ' disabled="disabled"' : "";
265       if (!($disabled && ($ewiki_ring>=2)))
266       $rs[$i] .= ' <input type="checkbox" value="1" name="perm_unix_rights['.$n.']"'
267           . $checked . $disabled . '>';
268    }
269    if ($rs[4]) { $rs[4] = "aaa ".$rs[4]; }
270    if ($rs[3]) { $rs[3] = "mmm ".$rs[3]; }
271
272    #-- output
273    $o = ewiki_t(<<<END
274 <table border="0" class="access-rights">
275 <tr><th align="left">_{access rights}&nbsp;&nbsp; <br />
276          {$rs[4]} </th>
277     <td><b>_{owner}</b> <input type="text" name="perm_unix_owner" size="10" value="{$owner}"></td>
278     <td><b>_{groups}</b> <input type="text" name="perm_unix_groups" size="12" value="{$groups}"></td>
279     <td><b>_{world}</b> </td> </tr>
280 <tr><td> {$rs[3]} </td>
281     <td> rwx {$rs[2]} </td>
282     <td> rwx {$rs[1]} </td>
283     <td> rwx {$rs[0]} </td>
284 </tr></table>
285 END
286    );
287    return($o);
288 }
289
290
291
292 /*
293    This part stores the changes from the edit/ screen back into the
294    database, thereby sanitizing the input and rejecting invalid changes.
295    Only the "owner" has permission to change _all_ settings, but members of
296    one of the mentioned groups can also change (revoke) access rights for
297    "groups" and "world".
298 */
299 #-- adds changed page access rights into {meta} - to get stored into database
300 function ewiki_edit_save_unix_access_rights(&$save, &$old_data) {
301
302    global $ewiki_auth_user, $ewiki_author, $ewiki_ring, $ewiki_errmsg;
303
304    ($user = $ewiki_auth_user)
305    or ($user = $ewiki_author)
306    or ($user = "");
307
308    #-- get current settings
309    $new_owner = $p_owner = $save["meta"]["owner"];
310    $new_groups = $p_groups = $save["meta"]["groups"];
311    ($p_rights = $save["meta"]["rights"]) or ($p_rights = 0777 ^ EWIKI_UNIX_PERM_UMAKS);
312    $new_rights = $p_rights;
313
314    #-- fetch entered settings
315    if (true) {
316
317       #-- access rights
318       $set_rights = 0;
319       foreach ($_POST["perm_unix_rights"] as $n=>$is) {
320          if ($is) {
321             $set_rights = $set_rights | (1<<$n);
322          }
323       }
324
325       #-- attempt to set new page owner?
326       $set_owner = trim($_REQUEST["perm_unix_owner"]);
327       if (!preg_match("/^[".EWIKI_CHARS."]+$/", $set_owner) && ewiki_auth_user_exists($set_owner)) {
328          $save = array();
329          $ewiki_errmsg = "broken username";
330          return;
331       }
332
333       #-- clean up list of group names
334       $set_groups = array();
335       $uu = strtr($_REQUEST["perm_unix_groups"], EWIKI_USERDB_GROUPDELIMS, "        ");
336       foreach (explode(" ", $uu) as $grp) {
337          if (strlen($grp) && preg_match("/^[".EWIKI_CHARS."]+$/", $grp)  /*&& group_exists()*/  ) {
338             $set_groups[] = $grp;
339          }
340       }
341       $set_groups = implode(", ", $set_groups);
342    }
343
344    #-- check if changing settings is allowed
345    $bad = 0;
346    if (!$user || ($user!=$p_owner)) {
347       $bad = $bad || ($p_owner != $set_owner) && !(
348          (empty($p_owner) && EWIKI_PERM_USERSET_FREE)
349          || ($set_owner == $user)
350       );
351 #     $bad = $bad || ($p_groups != $set_groups);   # cannot reliably check this
352       $bad = $bad || ($p_rights != $set_rights);
353    }
354    if ($ewiki_ring == 0) {
355       $bad = 0;
356    }
357 #$bad=0;
358
359    #-- login_query, if something was changed
360    if (($bad)) {
361       $uu = 0;
362       ewiki_auth($uu, $uu, $uu, $uu, 2);
363       $save = array();
364       $ewiki_errmsg = "You cannot change these settings without being logged in.";
365       return;
366    }
367 #echo "pr=$p_rights,sr=$set_rights<br />\n";
368 #echo "po=$p_owner,so=$set_owner<br />\n";
369 #echo "bad=$bad<br />\n";
370 #die("passed");
371
372
373    #-- the owner may change anything
374    if (($user == $p_owner) || (0 == $ewiki_ring)) {
375
376       #-- new page owner
377       $new_owner = $set_owner;   
378
379       #-- new access rights
380       $new_rights = $set_rights;
381       $new_rights |= 000400;  // owner can always edit a page
382
383       #-- restricted settings
384       if ($ewiki_ring == 0) {
385          // keep all bits
386       }
387       elseif ($ewiki_ring == 1) {
388          $new_rights = (($new_rights) & 007777) | (($p_rights) & 070000);
389       }
390       else {
391          $new_rights = (($new_rights) & 000777) | (($p_rights) & 077000);
392       }
393
394       #-- groups
395       $new_groups = $set_groups;
396    }
397
398 /********
399    #-- groups can...(?)
400    if (!EWIKI_PERM_UNIX_SHAREHOLDERS) {
401      // no they can't.
402    }
403    elseif (ewiki_auth_user_in_groups_str($user, $p_groups) && ($new_rights=$save["meta"]["rights"])) {
404       #-- this can effectively only remove bits
405       $new_rights = (077700 & $new_rights) | (000077 & $new_rights & $set_rights);
406    }
407 *******/
408
409    #-- store any changes into database entry
410    if ($user) {
411       $save["meta"]["rights"] = $new_rights;
412       $save["meta"]["owner"] = $new_owner;
413       $save["meta"]["groups"] = $new_groups;
414    }
415
416 #print_r($save);die();
417 }
418
419
420
421
422 //@FIXME: does not work with all ["auth_userdb"] plugins
423 //(no workaround, but safe to use with system/UserRegistry or system/passwd)
424 function ewiki_auth_user_exists($user) {
425    global $ewiki_plugins;
426    if ($pf_u = $ewiki_plugins["auth_userdb"])
427    foreach ($pf_u as $pf) {
428       if ($pf($user, '$0$')) {
429          return(true);
430       }
431    }
432    return(false);
433 }
434
435
436
437
438 #-- fetch groups for user
439 function ewiki_auth_get_users_groups($user) {
440
441    #-- get "system/groups" page
442    $gp = ewiki_db::GET(EWIKI_USERDB_SYSTEMGROUPS);
443    if (($gp["version"]) && !($gp["flags"] & EWIKI_DB_F_SYSTEM)) {
444       $gp["flags"] |= EWIKI_DB_F_SYSTEM;
445       $gp["version"]++;
446       ewiki_db::WRITE($gp);   // secure it as _SYSTEM page
447    }
448
449    $user_groups = array();
450    if (empty($user)) { return($user_groups); }
451
452    #-- go through all the lines in the groups file
453    if ($list = explode("\n", $gp["content"]))
454    foreach ($list as $line) {
455
456       #--- quick initial check for string occourence
457       if (strpos($line, $user)) {
458
459          #-- break line into parts
460          $line = trim($line);
461          if ($line[0] == "#") { 
462             continue;
463          }
464          if (!($group = strtok($line, ":"))) { 
465             continue;
466          }
467          $uu = strtok(":");  #-- this removes the "*:" we don't care
468          $line = strtok("\000");
469          if (strlen($uu) > 1) { $line .= " $uu"; }
470
471          #-- convert delimeters into spaces and lookup $user string
472          $line = strtr($line, EWIKI_USERDB_GROUPDELIMS, "        ");
473          if (strpos(" {$line} ", " {$user} ")) {
474             $user_groups[] = $group;
475          }
476       }
477    }
478
479    return($user_groups);
480 }
481
482
483
484 /* 
485    Retrieves the list of groups the $user belongs to, and compares against
486    the list in the $groups list string.
487 */
488 function ewiki_auth_user_in_groups_str($user, $groups="") {
489    $p_groups = explode(" ", strtr($groups, EWIKI_USERDB_GROUPDELIMS, "        "));
490    $user_groups = ewiki_auth_get_users_groups($user);
491    foreach ($user_groups as $ugrp) {
492       if (strlen($ugrp) && in_array($ugrp, $p_groups)) {
493          return(true);
494          break;
495       }
496    }
497    return(false);
498 }
499
500
501
502 ?>