# HG changeset patch # User Dan # Date 1206831539 25200 # Node ID c15fbf197a5494e50527412d9e5af6a2d4c2b5a1 # Parent 13532b0a223fd0ba1ad9e420eb060d17631ada57 AJAX interface for listing ACL rules implemented diff -r 13532b0a223f -r c15fbf197a54 includes/clientside/static/acl.js --- a/includes/clientside/static/acl.js Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/clientside/static/acl.js Sat Mar 29 15:58:59 2008 -0700 @@ -79,6 +79,13 @@ if ( !document.getElementById(aclManagerID) ) { __aclBuildWizardWindow(); + var main = document.getElementById(aclManagerID + '_main'); + main.style.padding = '10px'; + } + else + { + var main = document.getElementById(aclManagerID + '_main'); + main.style.backgroundImage = 'none'; } if ( response.mode == 'error' ) { @@ -87,7 +94,7 @@ return false; } aclDataCache = response; - aclBuildRuleEditor(response); + aclBuildRuleEditor(response, true); } }, true); } @@ -131,10 +138,30 @@ thispage = strToPageID(title); do_scopesel = ( thispage[0] == groups.page_id && thispage[1] == groups.namespace ); + document.getElementById(aclManagerID + '_next').style.display = 'inline'; + seed = Math.floor(Math.random() * 1000000); main = document.getElementById(aclManagerID + '_main'); main.style.padding = '10px'; + main.style.backgroundImage = 'none'; + + // the "edit existing" button + var editbtn_wrapper = document.createElement('div'); + editbtn_wrapper.style.styleFloat = 'right'; + editbtn_wrapper.style.cssFloat = 'right'; + editbtn_wrapper.style.fontSize = 'smaller'; + var editbtn = document.createElement('a'); + editbtn.href = '#'; + editbtn.innerHTML = $lang.get('acl_btn_show_existing'); + editbtn_wrapper.appendChild(editbtn); + main.appendChild(editbtn_wrapper); + + editbtn.onclick = function() + { + aclSetViewListExisting(); + return false; + } selector = document.createElement('div'); @@ -363,10 +390,14 @@ handle_invalid_json(ajax.responseText); return false; } - try { - data = parseJSON(ajax.responseText); - } catch(e) { + try + { + var data = parseJSON(ajax.responseText); + } + catch(e) + { handle_invalid_json(ajax.responseText); + return false; } aclDataCache = data; switch(data.mode) @@ -420,6 +451,7 @@ document.getElementById(aclManagerID + '_main').innerHTML += '
' + $lang.get('acl_lbl_deleterule') + '
'; document.getElementById(aclManagerID+'_main').scrollTop = 0; + document.getElementById(aclManagerID+'_main').style.backgroundImage = 'none'; aclDataCache.mode = 'save_edit'; break; @@ -476,6 +508,9 @@ case 'debug': aclDebug(data.text); break; + case 'list_existing': + aclSetViewListExistingRespond(data); + break; default: handle_invalid_json(ajax.responseText); break; @@ -484,13 +519,15 @@ }, true); } -function aclBuildRuleEditor(data) +function aclBuildRuleEditor(data, from_direct) { var act_desc = ( data.type == 'new' ) ? $lang.get('acl_lbl_editwin_title_create') : $lang.get('acl_lbl_editwin_title_edit'); var target_type_t = ( data.target_type == 1 ) ? $lang.get('acl_target_type_group') : $lang.get('acl_target_type_user'); var target_name_t = data.target_name; var scope_type = ( data.page_id == false && data.namespace == false ) ? $lang.get('acl_scope_type_wholesite') : ( data.namespace == '__PageGroup' ) ? $lang.get('acl_scope_type_pagegroup') : $lang.get('acl_scope_type_thispage'); + document.getElementById(aclManagerID + '_next').style.display = 'inline'; + html = '' + $lang.get('acl_lbl_editwin_body', { target_type: target_type_t, target: target_name_t, scope_type: scope_type }) + '
'; parser = new templateParser(data.template.acl_field_begin); @@ -558,11 +595,19 @@ var form = document.getElementById(aclManagerID + '_formobj_id'); - var modeobj = form_fetch_field(form, 'mode'); - if ( modeobj ) - modeobj.value = 'save_' + data.type; + if ( from_direct ) + { + var modeobj = document.getElementById(aclManagerID + '_mode'); + modeobj.value = 'save_edit'; + } else - alert('modeobj is invalid: '+modeobj); + { + var modeobj = form_fetch_field(form, 'mode'); + if ( modeobj ) + modeobj.value = 'save_' + data.type; + else + alert('modeobj is invalid: '+modeobj); + } aclPermList = array_keys(data.acl_types); @@ -918,6 +963,67 @@ __aclJSONSubmitAjaxHandler(parms); } +function aclSetViewListExisting() +{ + if ( !document.getElementById(aclManagerID) ) + { + return false; + } + + var main = document.getElementById(aclManagerID + '_main'); + main.innerHTML = ''; + main.style.backgroundImage = 'url(' + scriptPath + '/images/loading-big.gif)'; + main.style.backgroundRepeat = 'no-repeat'; + main.style.backgroundPosition = 'center center'; + + var parms = { + 'mode' : 'list_existing' + }; + __aclJSONSubmitAjaxHandler(parms); +} + +function aclSetViewListExistingRespond(data) +{ + var main = document.getElementById(aclManagerID + '_main'); + main.style.padding = '10px'; + main.innerHTML = ''; + + var heading = document.createElement('h3'); + heading.appendChild(document.createTextNode($lang.get('acl_msg_scale_intro_title'))); + main.appendChild(heading); + + var p = document.createElement('p'); + p.appendChild(document.createTextNode($lang.get('acl_msg_scale_intro_body'))); + main.appendChild(p); + + + main.innerHTML += data.key; + main.style.backgroundImage = 'none'; + + document.getElementById(aclManagerID + '_back').style.display = 'inline'; + document.getElementById(aclManagerID + '_next').style.display = 'none'; + + for ( var i = 0; i < data.rules.length; i++ ) + { + var rule = data.rules[i]; + // build the rule, this is just more boring DOM crap. + var div = document.createElement('div'); + div.style.padding = '5px 3px'; + div.style.backgroundColor = '#' + rule.color; + div.style.cursor = 'pointer'; + div.rule_id = rule.rule_id; + div.onclick = function() + { + var main = document.getElementById(aclManagerID + '_main'); + main.innerHTML = ''; + main.style.backgroundImage = 'url(' + scriptPath + '/images/loading-big.gif)'; + ajaxOpenDirectACLRule(parseInt(this.rule_id)); + } + div.innerHTML = rule.score_string; + main.appendChild(div); + } +} + function array_keys(obj) { keys = new Array(); diff -r 13532b0a223f -r c15fbf197a54 includes/clientside/static/misc.js --- a/includes/clientside/static/misc.js Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/clientside/static/misc.js Sat Mar 29 15:58:59 2008 -0700 @@ -589,14 +589,19 @@ var sets = document.getElementsByTagName('fieldset'); if ( sets.length < 1 ) return false; - for ( var i = 0; i < sets.length; i++ ) + var init_us = []; + for ( var index = 0; index < sets.length; index++ ) { - var mode = sets[i].getAttribute('enano:expand'); + var mode = sets[index].getAttribute('enano:expand'); if ( mode == 'closed' || mode == 'open' ) { - expander_init_element(sets[i]); + init_us.push(sets[index]); } } + for ( var k = 0; k < init_us.length; k++ ) + { + expander_init_element(init_us[k]); + } } function expander_init_element(el) @@ -624,6 +629,7 @@ } catch(e) { + console.debug('Exception caught: ', e); } return false; } @@ -662,8 +668,11 @@ $(a).addClass('expander-closed'); continue; } - child.expander_meta_old_state = child.style.display; - child.style.display = 'none'; + if ( child.style ) + { + child.expander_meta_old_state = child.style.display; + child.style.display = 'none'; + } } el.expander_meta_padbak = el.style.padding; el.setAttribute('enano:expand', 'closed'); @@ -682,14 +691,17 @@ $(a).addClass('expander-open'); continue; } - if ( child.expander_meta_old_state ) + if ( child.expander_meta_old_state && child.style ) { child.style.display = child.expander_meta_old_state; child.expander_meta_old_state = null; } else { - child.style.display = null; + if ( child.style ) + { + child.style.display = null; + } } } if ( el.expander_meta_padbak ) diff -r 13532b0a223f -r c15fbf197a54 includes/constants.php --- a/includes/constants.php Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/constants.php Sat Mar 29 15:58:59 2008 -0700 @@ -29,6 +29,11 @@ define('ACL_TYPE_USER', 2); define('ACL_TYPE_PRESET', 3); +// ACL color scale minimal shade for red and green ends +// The lower, the more saturated the color on the scale. +// Purely cosmetic. 0x0 - 0xFF, 0xFF will basically disable the scale +define('ACL_SCALE_MINIMAL_SHADE', 0xA8); + // ACL switch // If this is defined, administrators can edit ACLs regardless of current // permissions. This is enabled by default. diff -r 13532b0a223f -r c15fbf197a54 includes/pageutils.php --- a/includes/pageutils.php Thu Mar 27 16:41:07 2008 -0400 +++ b/includes/pageutils.php Sat Mar 29 15:58:59 2008 -0700 @@ -1672,11 +1672,15 @@ // regenerate page selection $parms['page_id'] = ( isset($parms['page_id']) ) ? $parms['page_id'] : false; $parms['namespace'] = ( isset($parms['namespace']) ) ? $parms['namespace'] : false; + $parms['mode'] = 'seltarget_id'; $page_id =& $parms['page_id']; $namespace =& $parms['namespace']; $page_where_clause = ( empty($page_id) || empty($namespace) ) ? 'AND a.page_id IS NULL AND a.namespace IS NULL' : 'AND a.page_id=\'' . $db->escape($page_id) . '\' AND a.namespace=\'' . $db->escape($namespace) . '\''; $page_where_clause_lite = ( empty($page_id) || empty($namespace) ) ? 'AND page_id IS NULL AND namespace IS NULL' : 'AND page_id=\'' . $db->escape($page_id) . '\' AND namespace=\'' . $db->escape($namespace) . '\''; + $return['page_id'] = $parms['page_id']; + $return['namespace'] = $parms['namespace']; + // From here, let the seltarget handler take over case 'seltarget': $return['mode'] = 'seltarget'; @@ -1688,24 +1692,25 @@ switch($parms['target_type']) { case ACL_TYPE_USER: - $q = $db->sql_query('SELECT a.rules,u.user_id FROM ' . table_prefix.'users AS u + $user_col = ( $parms['mode'] == 'seltarget_id' ) ? 'user_id' : 'username'; + $q = $db->sql_query('SELECT a.rules,u.user_id,u.username FROM ' . table_prefix.'users AS u LEFT JOIN ' . table_prefix.'acl AS a ON a.target_id=u.user_id WHERE a.target_type='.ACL_TYPE_USER.' - AND u.username=\'' . $db->escape($parms['target_id']) . '\' + AND u.' . $user_col . ' = \'' . $db->escape($parms['target_id']) . '\' ' . $page_where_clause . ';'); if(!$q) return(Array('mode'=>'error','error'=>$db->get_error())); if($db->numrows() < 1) { $return['type'] = 'new'; - $q = $db->sql_query('SELECT user_id FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';'); + $q = $db->sql_query('SELECT user_id,username FROM ' . table_prefix.'users WHERE username=\'' . $db->escape($parms['target_id']) . '\';'); if(!$q) return(Array('mode'=>'error','error'=>$db->get_error())); if($db->numrows() < 1) - return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found')); + return Array('mode'=>'error','error'=>$lang->get('acl_err_user_not_found'),'debug' => $db->sql_backtrace()); $row = $db->fetchrow(); - $return['target_name'] = $return['target_id']; + $return['target_name'] = $row['username']; $return['target_id'] = intval($row['user_id']); $return['current_perms'] = array(); } @@ -1713,7 +1718,7 @@ { $return['type'] = 'edit'; $row = $db->fetchrow(); - $return['target_name'] = $return['target_id']; + $return['target_name'] = $row['username']; $return['target_id'] = intval($row['user_id']); $return['current_perms'] = $session->string_to_perm($row['rules']); } @@ -1830,8 +1835,9 @@ { return Array('mode'=>'error','error'=>$lang->get('acl_err_demo')); } - $q = $db->sql_query('DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).' - ' . $page_where_clause_lite . ';'); + $sql = 'DELETE FROM ' . table_prefix.'acl WHERE target_type='.intval($parms['target_type']).' AND target_id='.intval($parms['target_id']).' + ' . $page_where_clause_lite . ';'; + $q = $db->sql_query($sql); if(!$q) return Array('mode'=>'error','error'=>$db->get_error()); return Array( @@ -1843,6 +1849,117 @@ 'namespace' => $namespace, ); break; + case 'list_existing': + + $return = array( + 'mode' => 'list_existing', + 'key' => acl_list_draw_key(), + 'rules' => array() + ); + + $q = $db->sql_query("SELECT a.rule_id, u.username, g.group_name, a.target_type, a.target_id, a.page_id, a.namespace, a.rules, p.pg_name\n" + . " FROM " . table_prefix . "acl AS a\n" + . " LEFT JOIN " . table_prefix . "users AS u\n" + . " ON ( (a.target_type = " . ACL_TYPE_USER . " AND a.target_id = u.user_id) OR (u.user_id IS NULL) )\n" + . " LEFT JOIN " . table_prefix . "groups AS g\n" + . " ON ( (a.target_type = " . ACL_TYPE_GROUP . " AND a.target_id = g.group_id) OR (g.group_id IS NULL) )\n" + . " LEFT JOIN " . table_prefix . "page_groups as p\n" + . " ON ( (a.namespace = '__PageGroup' AND a.page_id = p.pg_id) OR (p.pg_id IS NULL) )\n" + . " GROUP BY a.rule_id\n" + . " ORDER BY a.target_type ASC, a.rule_id ASC;" + ); + + if ( !$q ) + $db->_die(); + + while ( $row = $db->fetchrow($q) ) + { + if ( $row['target_type'] == ACL_TYPE_USER && empty($row['username']) ) + { + // This is only done if we have an ACL affecting a user that doesn't exist. + // Nice little bit of maintenance to have. + if ( !$db->sql_query("DELETE FROM " . table_prefix . "acl WHERE rule_id = {$row['rule_id']};") ) + $db->_die(); + continue; + } + $score = get_acl_rule_score($row['rules']); + $deep_limit = ACL_SCALE_MINIMAL_SHADE; + // Determine background color of cell by score + if ( $score > 5 ) + { + // high score, show in green + $color = 2.5 * $score; + if ( $color > 255 ) + $color = 255; + $color = round($color); + // blend with the colordepth limit + $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) ); + $color = dechex($color); + $color = "{$color}ff{$color}"; + } + else if ( $score < -5 ) + { + // low score, show in red + $color = 0 - $score; + $color = 2.5 * $color; + if ( $color > 255 ) + $color = 255; + $color = round($color); + // blend with the colordepth limit + $color = $deep_limit + ( ( 0xFF - $deep_limit ) - ( ( $color / 0xFF ) * ( 0xFF - $deep_limit ) ) ); + $color = dechex($color); + $color = "ff{$color}{$color}"; + } + else + { + $color = 'efefef'; + } + + // Rate rule textually based on its score + if ( $score >= 70 ) + $desc = $lang->get('acl_msg_scale_allow'); + else if ( $score >= 50 ) + $desc = $lang->get('acl_msg_scale_mostly_allow'); + else if ( $score >= 25 ) + $desc = $lang->get('acl_msg_scale_some_allow'); + else if ( $score >= -25 ) + $desc = $lang->get('acl_msg_scale_mixed'); + else if ( $score <= -70 ) + $desc = $lang->get('acl_msg_scale_deny'); + else if ( $score <= -50 ) + $desc = $lang->get('acl_msg_scale_mostly_deny'); + else if ( $score <= -25 ) + $desc = $lang->get('acl_msg_scale_some_deny'); + + // group and user target info + $info = ''; + if ( $row['target_type'] == ACL_TYPE_USER ) + $info = $lang->get('acl_msg_list_user', array( 'username' => $row['username'] )); // "(User: {$row['username']})"; + else if ( $row['target_type'] == ACL_TYPE_GROUP ) + $info = $lang->get('acl_msg_list_group', array( 'group' => $row['group_name'] )); + + // affected pages info + if ( $row['page_id'] && $row['namespace'] && $row['namespace'] != '__PageGroup' ) + $info .= $lang->get('acl_msg_list_on_page', array( 'page_name' => "{$row['namespace']}:{$row['page_id']}" )); + else if ( $row['page_id'] && $row['namespace'] && $row['namespace'] == '__PageGroup' ) + $info .= $lang->get('acl_msg_list_on_page_group', array( 'page_group' => $row['pg_name'] )); + else + $info .= $lang->get('acl_msg_list_entire_site'); + + $score_string = $lang->get('acl_msg_list_score', array + ( + 'score' => $score, + 'desc' => $desc, + 'info' => $info + )); + $return['rules'][] = array( + 'score_string' => $score_string, + 'rule_id' => $row['rule_id'], + 'color' => $color + ); + } + + break; default: return Array('mode'=>'error','error'=>'Hacking attempt'); break; @@ -2125,4 +2242,84 @@ } +/** + * Generates a graphical key showing how the ACL rule list works. + * @return string + */ + +function acl_list_draw_key() +{ + $out = '