Newer
Older
<?php
/**
* ACL administration functions
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Anika Henke <anika@selfthinker.org> (concepts)
* @author Frank Schubert <frank@schokilade.de> (old version)
/**
* All DokuWiki plugins to extend the admin function
* need to inherit from this class
*/
class admin_plugin_acl extends DokuWiki_Admin_Plugin {
/**
* The currently selected item, associative array with id and type.
* Populated from (in this order):
* $_REQUEST['current_ns']
* $_REQUEST['current_id']
* $ns
* $ID
*/
var $current_item = null;
Andreas Gohr
committed
var $specials = array();
/**
* return some info
*/
function getInfo(){
return array(
'author' => 'Andreas Gohr',
'email' => 'andi@splitbrain.org',
'url' => 'http://dokuwiki.org/plugin:acl',
/**
* return prompt for admin menu
*/
function getMenuText($language) {
/**
* return sort order for position in admin menu
*/
function getMenuSort() {
*
* Initializes internal vars and handles modifications
*
* @author Andreas Gohr <andi@splitbrain.org>
global $config_cascade;
// fresh 1:1 copy without replacements
$AUTH_ACL = file(DOKU_CONF.'acl.auth.php');
// namespace given?
if($_REQUEST['ns'] == '*'){
$this->ns = '*';
}else{
$this->ns = cleanID($_REQUEST['ns']);
}
if ($_REQUEST['current_ns']) {
$this->current_item = array('id' => cleanID($_REQUEST['current_ns']), 'type' => 'd');
} elseif ($_REQUEST['current_id']) {
$this->current_item = array('id' => cleanID($_REQUEST['current_id']), 'type' => 'f');
} elseif ($this->ns) {
$this->current_item = array('id' => $this->ns, 'type' => 'd');
} else {
$this->current_item = array('id' => $ID, 'type' => 'f');
}
// user or group choosen?
$who = trim($_REQUEST['acl_w']);
if($_REQUEST['acl_t'] == '__g__' && $who){
$this->who = '@'.ltrim($auth->cleanGroup($who),'@');
$this->who = ltrim($who,'@');
if($this->who != '%USER%'){ #keep wildcard as is
$this->who = $auth->cleanUser($this->who);
}
}elseif($_REQUEST['acl_t'] &&
$_REQUEST['acl_t'] != '__u__' &&
$_REQUEST['acl_t'] != '__g__'){
$this->who = $_REQUEST['acl_t'];
}elseif($who){
$this->who = $who;
}
// handle modifications
if(isset($_REQUEST['cmd']) && checkSecurityToken()){
// scope for modifications
if($this->ns){
if($this->ns == '*'){
$scope = '*';
}else{
$scope = $this->ns.':*';
}
}else{
$scope = $ID;
}
if(isset($_REQUEST['cmd']['save']) && $scope && $this->who && isset($_REQUEST['acl'])){
// handle additions or single modifications
$this->_acl_del($scope, $this->who);
$this->_acl_add($scope, $this->who, (int) $_REQUEST['acl']);
}elseif(isset($_REQUEST['cmd']['del']) && $scope && $this->who){
// handle single deletions
$this->_acl_del($scope, $this->who);
}elseif(isset($_REQUEST['cmd']['update'])){
// handle update of the whole file
foreach((array) $_REQUEST['del'] as $where => $names){
foreach($names as $who)
unset($_REQUEST['acl'][$where][$who]);
}
// prepare lines
$lines = array();
// keep header
foreach($AUTH_ACL as $line){
if($line{0} == '#'){
$lines[] = $line;
}else{
break;
}
}
// re-add all rules
foreach((array) $_REQUEST['acl'] as $where => $opt){
foreach($opt as $who => $perm){
if ($who[0]=='@') {
if ($who!='@ALL') {
$who = '@'.ltrim($auth->cleanGroup($who),'@');
}
} elseif ($who != '%USER%'){ #keep wildcard as is
$who = $auth->cleanUser($who);
}
$who = auth_nameencode($who,true);
$lines[] = "$where\t$who\t$perm\n";
}
}
// save it
io_saveFile($config_cascade['acl']['default'], join('',$lines));
$AUTH_ACL = file($config_cascade['acl']['default']);
}
// initialize ACL array
$this->_init_acl_config();
/**
* ACL Output function
*
* print a table with all significant permissions for the
* current id
*
* @author Frank Schubert <frank@schokilade.de>
* @author Andreas Gohr <andi@splitbrain.org>
*/
function html() {
echo '<div id="acl_manager">'.NL;
echo '<h1>'.$this->getLang('admin_acl').'</h1>'.NL;
echo '<div class="level1">'.NL;
$this->_html_explorer();
echo '<div id="acl__detail">'.NL;
$this->_html_detail();
echo '</div>'.NL;
echo '</div>'.NL;
echo '<div class="clearer"></div>';
echo '<h2>'.$this->getLang('current').'</h2>'.NL;
echo '<div class="level2">'.NL;
$this->_html_table();
echo '</div>'.NL;
echo '<div class="footnotes"><div class="fn">'.NL;
echo '<sup><a id="fn__1" class="fn_bot" name="fn__1" href="#fnt__1">1)</a></sup>'.NL;
echo $this->getLang('p_include');
echo '</div></div>';
* returns array with set options for building links
* @author Andreas Gohr <andi@splitbrain.org>
*/
function _get_opts($addopts=null){
global $ID;
$opts = array(
'do'=>'admin',
'page'=>'acl',
);
if($this->ns) $opts['ns'] = $this->ns;
if($this->who) $opts['acl_w'] = $this->who;
if(is_null($addopts)) return $opts;
return array_merge($opts, $addopts);
}
/**
* Display a tree menu to select a page or namespace
function _html_explorer(){
global $conf;
global $ID;
global $lang;
$dir = $conf['datadir'];
$ns = $this->ns;
if(empty($ns)){
$ns = dirname(str_replace(':','/',$ID));
if($ns == '.') $ns ='';
}elseif($ns == '*'){
$ns ='';
// wrap a list with the root level around the other namespaces
$item = array( 'level' => 0, 'id' => '*', 'type' => 'd',
'open' =>'true', 'label' => '['.$lang['mediaroot'].']');
echo '<ul class="acltree">';
echo $this->_html_li_acl($item);
echo '<div class="li">';
echo $this->_html_list_acl($item);
echo '</div>';
echo html_buildlist($data,'acl',
array($this,'_html_list_acl'),
array($this,'_html_li_acl'));
echo '</li>';
echo '</ul>';
}
/**
* get a combined list of media and page files
*
* @param string $folder an already converted filesystem folder of the current namespace
* @param string $limit limit the search to this folder
*/
function _get_tree($folder,$limit=''){
global $conf;
// read tree structure from pages and media
$data = array();
search($data,$conf['datadir'],'search_index',array('ns' => $folder),$limit);
$media = array();
search($media,$conf['mediadir'],'search_index',array('ns' => $folder, 'nofiles' => true),$limit);
$data = array_merge($data,$media);
unset($media);
// combine by sorting and removing duplicates
usort($data,array($this,'_tree_sort'));
$count = count($data);
if($count>0) for($i=1; $i<$count; $i++){
if($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) unset($data[$i]);
}
return $data;
}
/**
* usort callback
*
* Sorts the combined trees of media and page files
*/
function _tree_sort($a,$b){
// handle the trivial cases first
if ($a['id'] == '') return -1;
if ($b['id'] == '') return 1;
// split up the id into parts
$a_ids = explode(':', $a['id']);
$b_ids = explode(':', $b['id']);
// now loop through the parts
while (count($a_ids) && count($b_ids)) {
// compare each level from upper to lower
// until a non-equal component is found
$cur_result = strcmp(array_shift($a_ids), array_shift($b_ids));
if ($cur_result) {
// if one of the components is the last component and is a file
// and the other one is either of a deeper level or a directory,
// the file has to come after the deeper level or directory
if (empty($a_ids) && $a['type'] == 'f' && (count($b_ids) || $b['type'] == 'd')) return 1;
if (empty($b_ids) && $b['type'] == 'f' && (count($a_ids) || $a['type'] == 'd')) return -1;
return $cur_result;
}
}
// The two ids seem to be equal. One of them might however refer
// to a page, one to a namespace, the namespace needs to be first.
if (empty($a_ids) && empty($b_ids)) {
if ($a['type'] == $b['type']) return 0;
if ($a['type'] == 'f') return 1;
// Now the empty part is either a page in the parent namespace
// that obviously needs to be after the namespace
// Or it is the namespace that contains the other part and should be
// before that other part.
if (empty($a_ids)) return ($a['type'] == 'd') ? -1 : 1;
if (empty($b_ids)) return ($b['type'] == 'd') ? 1 : -1;
/**
* Display the current ACL for selected where/who combination with
* selectors and modification form
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function _html_detail(){
global $conf;
global $ID;
echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL;
echo '<div id="acl__user">';
echo $this->getLang('acl_perms').' ';
$inl = $this->_html_select();
echo '<input type="text" name="acl_w" class="edit" value="'.(($inl)?'':hsc(ltrim($this->who,'@'))).'" />'.NL;
echo '<input type="submit" value="'.$this->getLang('btn_select').'" class="button" />'.NL;
echo '</div>'.NL;
echo '<div id="acl__info">';
$this->_html_info();
echo '</div>';
echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL;
echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL;
echo '<input type="hidden" name="do" value="admin" />'.NL;
echo '<input type="hidden" name="page" value="acl" />'.NL;
echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'.NL;
}
/**
* Print infos and editor
*/
function _html_info(){
global $ID;
if($this->who){
$current = $this->_get_exact_perm();
// explain current permissions
$this->_html_explain($current);
// load editor
$this->_html_acleditor($current);
}else{
echo '<p>';
if($this->ns){
printf($this->getLang('p_choose_ns'),hsc($this->ns));
}else{
printf($this->getLang('p_choose_id'),hsc($ID));
}
echo '</p>';
echo $this->locale_xhtml('help');
}
/**
* Display the ACL editor
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function _html_acleditor($current){
global $lang;
echo '<fieldset>';
if(is_null($current)){
echo '<legend>'.$this->getLang('acl_new').'</legend>';
}else{
echo '<legend>'.$this->getLang('acl_mod').'</legend>';
echo $this->_html_checkboxes($current,empty($this->ns),'acl');
if(is_null($current)){
echo '<input type="submit" name="cmd[save]" class="button" value="'.$lang['btn_save'].'" />'.NL;
}else{
echo '<input type="submit" name="cmd[save]" class="button" value="'.$lang['btn_update'].'" />'.NL;
echo '<input type="submit" name="cmd[del]" class="button" value="'.$lang['btn_delete'].'" />'.NL;
}
* Explain the currently set permissions in plain english/$lang
function _html_explain($current){
global $ID;
global $auth;
$who = $this->who;
$ns = $this->ns;
// prepare where to check
if($ns){
if($ns == '*'){
$check='*';
}else{
$check=$ns.':*';
}
}else{
$check = $ID;
}
// prepare who to check
if($who{0} == '@'){
$user = '';
$groups = array(ltrim($who,'@'));
}else{
$user = auth_nameencode($who);
$info = $auth->getUserData($user);
if($info === false){
$groups = array();
}else{
$groups = $info['grps'];
// check the permissions
$perm = auth_aclcheck($check,$user,$groups);
// build array of named permissions
$names = array();
if($perm){
if($ns){
if($perm >= AUTH_DELETE) $names[] = $this->getLang('acl_perm16');
if($perm >= AUTH_UPLOAD) $names[] = $this->getLang('acl_perm8');
if($perm >= AUTH_CREATE) $names[] = $this->getLang('acl_perm4');
}
if($perm >= AUTH_EDIT) $names[] = $this->getLang('acl_perm2');
if($perm >= AUTH_READ) $names[] = $this->getLang('acl_perm1');
$names = array_reverse($names);
}else{
$names[] = $this->getLang('acl_perm0');
}
// print permission explanation
echo '<p>';
if($user){
if($ns){
printf($this->getLang('p_user_ns'),hsc($who),hsc($ns),join(', ',$names));
}else{
printf($this->getLang('p_user_id'),hsc($who),hsc($ID),join(', ',$names));
}
}else{
if($ns){
printf($this->getLang('p_group_ns'),hsc(ltrim($who,'@')),hsc($ns),join(', ',$names));
}else{
printf($this->getLang('p_group_id'),hsc(ltrim($who,'@')),hsc($ID),join(', ',$names));
}
}
echo '</p>';
// add note if admin
if($perm == AUTH_ADMIN){
echo '<p>'.$this->getLang('p_isadmin').'</p>';
}elseif(is_null($current)){
echo '<p>'.$this->getLang('p_inherited').'</p>';
}
* User function for html_buildlist()
*
* @author Andreas Gohr <andi@splitbrain.org>
function _html_list_acl($item){
global $ID;
$ret = '';
// what to display
if($item['label']){
$base = $item['label'];
}else{
$base = ':'.$item['id'];
$base = substr($base,strrpos($base,':')+1);
}
if( ($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id']))
$cl = ' cur';
// namespace or page?
if($item['type']=='d'){
if($item['open']){
$img = DOKU_BASE.'lib/images/minus.gif';
$alt = '−';
}else{
$img = DOKU_BASE.'lib/images/plus.gif';
$alt = '+';
}
$ret .= '<img src="'.$img.'" alt="'.$alt.'" />';
$ret .= '<a href="'.wl('',$this->_get_opts(array('ns'=>$item['id'],'sectok'=>getSecurityToken()))).'" class="idx_dir'.$cl.'">';
$ret .= '<a href="'.wl('',$this->_get_opts(array('id'=>$item['id'],'ns'=>'','sectok'=>getSecurityToken()))).'" class="wikilink1'.$cl.'">';
$ret .= noNS($item['id']);
$ret .= '</a>';
}
return $ret;
}
function _html_li_acl($item){
return '<li class="level'.$item['level'].'">';
function _init_acl_config(){
global $AUTH_ACL;
global $conf;
$acl_config=array();
$usersgroups = array();
Andreas Gohr
committed
// get special users and groups
$this->specials[] = '@ALL';
$this->specials[] = '@'.$conf['defaultgroup'];
if($conf['manager'] != '!!not set!!'){
$this->specials = array_merge($this->specials,
array_map('trim',
explode(',',$conf['manager'])));
}
$this->specials = array_filter($this->specials);
$this->specials = array_unique($this->specials);
sort($this->specials);
foreach($AUTH_ACL as $line){
$line = trim(preg_replace('/#.*$/','',$line)); //ignore comments
if(!$line) continue;
$acl = preg_split('/\s+/',$line);
//0 is pagename, 1 is user, 2 is acl
$acl[1] = rawurldecode($acl[1]);
$acl_config[$acl[0]][$acl[1]] = $acl[2];
// store non-special users and groups for later selection dialog
$ug = $acl[1];
Andreas Gohr
committed
if(in_array($ug,$this->specials)) continue;
$usersgroups = array_unique($usersgroups);
sort($usersgroups);
ksort($acl_config);
$this->acl = $acl_config;
$this->usersgroups = $usersgroups;
}
/**
* Display all currently set permissions in a table
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function _html_table(){
global $lang;
global $ID;
echo '<form action="'.wl().'" method="post" accept-charset="utf-8"><div class="no">'.NL;
if($this->ns){
echo '<input type="hidden" name="ns" value="'.hsc($this->ns).'" />'.NL;
}else{
echo '<input type="hidden" name="id" value="'.hsc($ID).'" />'.NL;
}
echo '<input type="hidden" name="acl_w" value="'.hsc($this->who).'" />'.NL;
echo '<input type="hidden" name="do" value="admin" />'.NL;
echo '<input type="hidden" name="page" value="acl" />'.NL;
echo '<input type="hidden" name="sectok" value="'.getSecurityToken().'" />'.NL;
echo '<table class="inline">';
echo '<tr>';
echo '<th>'.$this->getLang('where').'</th>';
echo '<th>'.$this->getLang('who').'</th>';
echo '<th>'.$this->getLang('perm').'<sup><a id="fnt__1" class="fn_top" name="fnt__1" href="#fn__1">1)</a></sup></th>';
echo '<th>'.$lang['btn_delete'].'</th>';
echo '</tr>';
foreach($this->acl as $where => $set){
foreach($set as $who => $perm){
echo '<tr>';
echo '<td>';
if(substr($where,-1) == '*'){
echo '<span class="aclns">'.hsc($where).'</span>';
$ispage = false;
}else{
echo '<span class="aclpage">'.hsc($where).'</span>';
$ispage = true;
}
echo '</td>';
echo '<td>';
if($who{0} == '@'){
echo '<span class="aclgroup">'.hsc($who).'</span>';
}else{
echo '<span class="acluser">'.hsc($who).'</span>';
}
echo '</td>';
echo '<td>';
echo $this->_html_checkboxes($perm,$ispage,'acl['.$where.']['.$who.']');
echo '<input type="checkbox" name="del['.hsc($where).'][]" value="'.hsc($who).'" />';
echo '<tr>';
echo '<th align="right" colspan="4">';
echo '<input type="submit" value="'.$lang['btn_update'].'" name="cmd[update]" class="button" />';
echo '</th>';
echo '</tr>';
echo '</table>';
* Returns the permission which were set for exactly the given user/group
* and page/namespace. Returns null if no exact match is available
function _get_exact_perm(){
global $ID;
if($this->ns){
if($this->ns == '*'){
$check = '*';
}else{
$check = $this->ns.':*';
}
}else{
$check = $ID;
}
Andreas Gohr
committed
if(isset($this->acl[$check][$this->who])){
return $this->acl[$check][$this->who];
*
* @author Frank Schubert <frank@schokilade.de>
*/
function _acl_add($acl_scope, $acl_user, $acl_level){
global $config_cascade;
$acl_config = file_get_contents($config_cascade['acl']['default']);
$acl_user = auth_nameencode($acl_user,true);
// max level for pagenames is edit
if(strpos($acl_scope,'*') === false) {
if($acl_level > AUTH_EDIT) $acl_level = AUTH_EDIT;
$new_acl = "$acl_scope\t$acl_user\t$acl_level\n";
$new_config = $acl_config.$new_acl;
return io_saveFile(DOKU_CONF.'acl.auth.php', $new_config);
/**
* remove acl-entry from conf/acl.auth.php
*
* @author Frank Schubert <frank@schokilade.de>
*/
function _acl_del($acl_scope, $acl_user){
global $config_cascade;
$acl_config = file($config_cascade['acl']['default']);
$acl_user = auth_nameencode($acl_user,true);
$acl_pattern = '^'.preg_quote($acl_scope,'/').'\s+'.$acl_user.'\s+[0-8].*$';
// save all non!-matching
$new_config = preg_grep("/$acl_pattern/", $acl_config, PREG_GREP_INVERT);
return io_saveFile(DOKU_CONF.'acl.auth.php', join('',$new_config));
}
*
* @author Frank Schubert <frank@schokilade.de>
* @author Andreas Gohr <andi@splitbrain.org>
*/
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
function _html_checkboxes($setperm,$ispage,$name){
global $lang;
static $label = 0; //number labels
$ret = '';
if($ispage && $setperm > AUTH_EDIT) $perm = AUTH_EDIT;
foreach(array(AUTH_NONE,AUTH_READ,AUTH_EDIT,AUTH_CREATE,AUTH_UPLOAD,AUTH_DELETE) as $perm){
$label += 1;
//general checkbox attributes
$atts = array( 'type' => 'radio',
'id' => 'pbox'.$label,
'name' => $name,
'value' => $perm );
//dynamic attributes
if(!is_null($setperm) && $setperm == $perm) $atts['checked'] = 'checked';
if($ispage && $perm > AUTH_EDIT){
$atts['disabled'] = 'disabled';
$class = ' class="disabled"';
}else{
$class = '';
}
//build code
$ret .= '<label for="pbox'.$label.'" title="'.$this->getLang('acl_perm'.$perm).'"'.$class.'>';
$ret .= '<input '.html_attbuild($atts).' /> ';
$ret .= $this->getLang('acl_perm'.$perm);
$ret .= '</label>'.NL;
}
return $ret;
/**
* Print a user/group selector (reusing already used users and groups)
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
function _html_select(){
global $conf;
$inlist = false;
if($this->who &&
!in_array($this->who,$this->usersgroups) &&
!in_array($this->who,$this->specials)){
if($this->who{0} == '@'){
$gsel = ' selected="selected"';
}else{
$usel = ' selected="selected"';
}
}else{
$usel = '';
$gsel = '';
$inlist = true;
}
echo '<select name="acl_t" class="edit">'.NL;
echo ' <option value="__g__" class="aclgroup"'.$gsel.'>'.$this->getLang('acl_group').':</option>'.NL;
echo ' <option value="__u__" class="acluser"'.$usel.'>'.$this->getLang('acl_user').':</option>'.NL;
echo ' <optgroup label=" ">'.NL;
Andreas Gohr
committed
foreach($this->specials as $ug){
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
if($ug == $this->who){
$sel = ' selected="selected"';
$inlist = true;
}else{
$sel = '';
}
if($ug{0} == '@'){
echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL;
}else{
echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL;
}
}
echo ' </optgroup>'.NL;
echo ' <optgroup label=" ">'.NL;
foreach($this->usersgroups as $ug){
if($ug == $this->who){
$sel = ' selected="selected"';
$inlist = true;
}else{
$sel = '';
}
if($ug{0} == '@'){
echo ' <option value="'.hsc($ug).'" class="aclgroup"'.$sel.'>'.hsc($ug).'</option>'.NL;
}else{
echo ' <option value="'.hsc($ug).'" class="acluser"'.$sel.'>'.hsc($ug).'</option>'.NL;
}
}
echo ' </optgroup>'.NL;
echo '</select>'.NL;
return $inlist;
}