Implemented all security features on theme disabling and ACLs; added clean_key mode to login API to clean unused encryption keys
authorDan
Sat, 01 Mar 2008 23:02:05 -0500
changeset 471 7906fb190fc1
parent 470 a044ad834691
child 472 bc4b58034f4d
Implemented all security features on theme disabling and ACLs; added clean_key mode to login API to clean unused encryption keys
includes/clientside/static/login.js
includes/sessions.php
includes/template.php
plugins/admin/ThemeManager.php
--- a/includes/clientside/static/login.js	Sat Mar 01 19:01:07 2008 -0500
+++ b/includes/clientside/static/login.js	Sat Mar 01 23:02:05 2008 -0500
@@ -110,6 +110,12 @@
           d.parentNode.removeChild(d);
         }, to);
     }
+    // Ask the server to clean our key
+    ajaxLoginPerformRequest({
+        mode: 'clean_key',
+        key_aes: logindata.key_aes,
+        key_dh: logindata.key_dh
+    });
   };
   
   logindata.mb_object.onbeforeclick['OK'] = function()
@@ -325,12 +331,12 @@
     new messagebox(MB_ICONSTOP | MB_OK, 'FIXME L10N: There was an error in the login process', 'The following error code came from the server:<br />' + response.error);
     return false;
   }
-  // Rid ourselves of any loading windows
-  ajaxLoginSetStatus(AJAX_STATUS_DESTROY);
   // Main mode switch
   switch ( response.mode )
   {
     case 'build_box':
+      // Rid ourselves of any loading windows
+      ajaxLoginSetStatus(AJAX_STATUS_DESTROY);
       // The server wants us to build the login form, all the information is there
       ajaxLoginBuildForm(response);
       break;
@@ -339,6 +345,8 @@
       logindata.successfunc(response.key);
       break;
     case 'login_failure':
+      // Rid ourselves of any loading windows
+      ajaxLoginSetStatus(AJAX_STATUS_DESTROY);
       document.getElementById('messageBox').style.backgroundColor = '#C0C0C0';
       var mb_parent = document.getElementById('messageBox').parentNode;
       new Spry.Effect.Shake(mb_parent, {duration: 1500}).start();
@@ -349,6 +357,8 @@
           ajaxLoginShowFriendlyError(response);
         }, 2500);
       break;
+    case 'noop':
+      break;
   }
 }
 
--- a/includes/sessions.php	Sat Mar 01 19:01:07 2008 -0500
+++ b/includes/sessions.php	Sat Mar 01 23:02:05 2008 -0500
@@ -551,14 +551,22 @@
         die('No group info');
       }
     }
+    
+    // make sure we aren't banned
     $this->check_banlist();
     
+    // Printable page view? Probably the wrong place to control
+    // it but $template is pretty dumb, it will just about always
+    // do what you ask it to do, which isn't always what we want
     if ( isset ( $_GET['printable'] ) )
     {
       $this->theme = 'printable';
       $this->style = 'default';
     }
     
+    // setup theme ACLs
+    $template->process_theme_acls();
+    
     profiler_log('Sessions started');
   }
   
@@ -3209,6 +3217,23 @@
         }
         
         break;
+      case 'clean_key':
+        // Clean out a key, since it won't be used.
+        if ( !empty($req['key_aes']) )
+        {
+          $this->fetch_public_key($req['key_aes']);
+        }
+        if ( !empty($req['key_dh']) )
+        {
+          $pk = $db->escape($req['key_dh']);
+          $q = $db->sql_query('DELETE FROM ' . table_prefix . "diffiehellman WHERE public_key = '$pk';");
+          if ( !$q )
+            $db->die_json();
+        }
+        return array(
+            'mode' => 'noop'
+          );
+        break;
     }
     
   }
--- a/includes/template.php	Sat Mar 01 19:01:07 2008 -0500
+++ b/includes/template.php	Sat Mar 01 23:02:05 2008 -0500
@@ -16,6 +16,14 @@
   var $tpl_strings, $tpl_bool, $theme, $style, $no_headers, $additional_headers, $sidebar_extra, $sidebar_widgets, $toolbar_menu, $theme_list, $named_theme_list, $default_theme, $default_style, $plugin_blocks, $namespace_string, $style_list, $theme_loaded;
   
   /**
+   * The list of themes that are critical for Enano operation. This doesn't include oxygen which
+   * remains a user theme. By default this is admin and printable which have to be loaded on demand.
+   * @var array
+   */
+  
+  var $system_themes = array('admin', 'printable');
+  
+  /**
    * Set to true if the site is disabled and thus a message needs to be shown. This should ONLY be changed by common.php.
    * @var bool
    * @access private
@@ -47,40 +55,139 @@
     
     $this->theme_list = Array();
     $this->named_theme_list = Array();
-    $e = $db->sql_query('SELECT theme_id,theme_name,enabled,default_style FROM '.table_prefix.'themes WHERE enabled=1 ORDER BY theme_order;');
-    if(!$e) $db->_die('The list of themes could not be selected.');
-    for($i=0;$i < $db->numrows(); $i++)
+    
+    $q = $db->sql_query('SELECT theme_id, theme_name, enabled, default_style, group_policy, group_list FROM ' . table_prefix . 'themes;');
+    if ( !$q )
+      $db->_die('template.php selecting theme list');
+    
+    $i = 0;
+    while ( $row = $db->fetchrow() )
     {
-      $this->theme_list[$i] = $db->fetchrow();
-      $this->named_theme_list[$this->theme_list[$i]['theme_id']] = $this->theme_list[$i];
+      $this->theme_list[$i] = $row;
+      $i++;
     }
-    $db->free_result();
-    $this->default_theme = $this->theme_list[0]['theme_id'];
-    $dir = ENANO_ROOT.'/themes/'.$this->default_theme.'/css/';
-    $list = Array();
-    // Open a known directory, and proceed to read its contents
-    if (is_dir($dir)) {
-      if ($dh = opendir($dir)) {
-        while (($file = readdir($dh)) !== false) {
-          if(preg_match('#^(.*?)\.css$#i', $file) && $file != '_printable.css') {
-            $list[] = substr($file, 0, strlen($file)-4);
-          }
+    // List out all CSS files for this theme
+    foreach ( $this->theme_list as $i => &$theme )
+    {
+      $theme['css'] = array();
+      $dir = ENANO_ROOT . "/themes/{$theme['theme_id']}/css";
+      if ( $dh = @opendir($dir) )
+      {
+        while ( ( $file = @readdir($dh) ) !== false )
+        {
+          if ( preg_match('/\.css$/', $file) )
+            $theme['css'][] = preg_replace('/\.css$/', '', $file);
         }
         closedir($dh);
       }
+      // No CSS files? If so, nuke it.
+      if ( count($theme['css']) < 1 )
+      {
+        unset($this->theme_list[$i]);
+      }
+    }
+    $this->theme_list = array_values($this->theme_list);
+    // Create associative array of themes
+    foreach ( $this->theme_list as $i => &$theme )
+      $this->named_theme_list[ $theme['theme_id'] ] =& $this->theme_list[$i];
+    
+    $this->default_theme = ( $_ = getConfig('theme_default') ) ? $_ : $this->theme_list[0]['theme_id'];
+    // Come up with the default style. If the CSS file specified in default_style exists, we're good, just
+    // use that. Otherwise, use the first stylesheet that comes to mind.
+    $df_data =& $this->named_theme_list[ $this->default_theme ];
+    $this->default_style = ( in_array($df_data['default_style'], $df_data['css']) ) ? $df_data['default_style'] : $df_data['css'][0];
+  }
+  
+  /**
+   * Systematically deletes themes if they're blocked by theme security settings. Called when session->start() finishes.
+   */
+  
+  function process_theme_acls()
+  {
+    global $db, $session, $paths, $template, $plugins; // Common objects
+    
+    // For each theme, check ACLs and delete from RAM if not authorized
+    foreach ( $this->theme_list as $i => $theme )
+    {
+      if ( !$theme['group_list'] )
+        continue;
+      switch ( $theme['group_policy'] )
+      {
+        case 'allow_all':
+          // Unconditionally allowed
+          continue;
+          break;
+        case 'whitelist':
+          // If we're not on the list, off to the left please
+          $list = enano_json_decode($theme['group_list']);
+          $allowed = false;
+          foreach ( $list as $acl )
+          {
+            if ( !preg_match('/^(u|g):([0-9]+)$/', $acl, $match) )
+              // Invalid list entry, silently allow (maybe not a good idea but
+              // really, these things are checked before they're inserted)
+              continue 2;
+            $mode = $match[1];
+            $id = intval($match[2]);
+            switch ( $mode )
+            {
+              case 'u':
+                $allowed = ( $id == $session->user_id );
+                if ( $allowed )
+                  break 2;
+                break;
+              case 'g':
+                $allowed = ( isset($session->groups[$id]) );
+                if ( $allowed )
+                  break 2;
+            }
+          }
+          if ( !$allowed )
+          {
+            unset($this->theme_list[$i]);
+          }
+          break;
+        case 'blacklist':
+          // If we're ON the list, off to the left please
+          $list = enano_json_decode($theme['group_list']);
+          $allowed = true;
+          foreach ( $list as $acl )
+          {
+            if ( !preg_match('/^(u|g):([0-9]+)$/', $acl, $match) )
+              // Invalid list entry, silently allow (maybe not a good idea but
+              // really, these things are checked before they're inserted)
+              continue 2;
+            $mode = $match[1];
+            $id = intval($match[2]);
+            switch ( $mode )
+            {
+              case 'u':
+                $allowed = ( $id != $session->user_id );
+                if ( !$allowed )
+                  break 2;
+                break;
+              case 'g':
+                $allowed = ( !isset($session->groups[$id]) );
+                if ( !$allowed )
+                  break 2;
+            }
+          }
+          if ( !$allowed )
+          {
+            unset($this->theme_list[$i]);
+          }
+          break;
+      }
     }
     
-    $def = ENANO_ROOT.'/themes/'.$this->default_theme.'/css/'.$this->named_theme_list[$this->default_theme]['default_style'];
-    if(file_exists($def))
-    {
-      $this->default_style = substr($this->named_theme_list[$this->default_theme]['default_style'], 0, strlen($this->named_theme_list[$this->default_theme]['default_style'])-4);
-    } else {
-      $this->default_style = $list[0];
-    }
+    $this->theme_list = array_values($this->theme_list);
     
-    $this->style_list = $list;
-    
+    // Rebuild associative theme list
+    $this->named_theme_list = array();
+    foreach ( $this->theme_list as $i => &$theme )
+      $this->named_theme_list[ $theme['theme_id'] ] =& $this->theme_list[$i];
   }
+  
   function sidebar_widget($t, $h, $use_normal_section = false)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
@@ -120,7 +227,7 @@
       echo "/* WARNING: Falling back to default file because file $path does not exist */\n";
       $path = 'css/' . $this->style_list[0] . '.css';
     }
-    return $this->process_template($path);
+    return '<enano:no-opt>' . $this->process_template($path) . '</enano:no-opt>';
   }
   function load_theme($name = false, $css = false)
   {
@@ -132,6 +239,26 @@
       $this->theme = $this->theme_list[0]['theme_id'];
       $this->style = preg_replace('/\.css$/', '', $this->theme_list[0]['default_style']);
     }
+    // Make sure we're allowed to use this theme.
+    if ( (
+        // If it was removed, it's probably blocked by an ACL, or it was uninstalled
+        !isset($this->named_theme_list[$this->theme]) ||
+        // Check if the theme is disabled
+        ( isset($this->named_theme_list[$this->theme]) && $this->named_theme_list[$this->theme]['enabled'] == 0 ) )
+        // Above all, if it's a system theme, don't inhibit the loading process.
+        && !in_array($this->theme, $this->system_themes)
+      )
+    {
+      // No, something is preventing it - fall back to site default
+      $this->theme = $this->default_theme;
+      
+      // Come up with the default style. If the CSS file specified in default_style exists, we're good, just
+      // use that. Otherwise, use the first stylesheet that comes to mind.
+      $df_data =& $this->named_theme_list[ $this->theme ];
+      $this->style = ( in_array($df_data['default_style'], $df_data['css']) ) ? $df_data['default_style'] : $df_data['css'][0];
+    }
+    // The list of styles for the currently selected theme
+    $this->style_list =& $this->named_theme_list[ $this->theme ]['css'];
     $this->theme_loaded = true;
   }
   
--- a/plugins/admin/ThemeManager.php	Sat Mar 01 19:01:07 2008 -0500
+++ b/plugins/admin/ThemeManager.php	Sat Mar 01 23:02:05 2008 -0500
@@ -24,7 +24,7 @@
     return;
   }
   
-  $system_themes = array('admin', 'printable');
+  $system_themes =& $template->system_themes;
   
   // Obtain the list of themes (both available and already installed) and the styles available for each
   $dh = @opendir(ENANO_ROOT . '/themes');
@@ -310,7 +310,7 @@
       if ( $enable == 0 && ( $theme_default === $theme_data['theme_id'] || $theme_data['make_default'] ) )
       {
         $enable = '1';
-        $warn_default .= $lang->get('acptm_warn_cant_disable_default');
+        $warn_default .= '<b>' . $lang->get('acptm_warn_cant_disable_default') . '</b>';
       }
       
       // We're good. Update the theme...