security.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: controller_2components_2security_8php-source.html 580 2008-07-01 14:45:49Z gwoo $ */
00003 /**
00004  * Short description for file.
00005  *
00006  * Long description for file
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) : Rapid Development Framework <http://www.cakephp.org/>
00011  * Copyright 2005-2008, Cake Software Foundation, Inc.
00012  *                              1785 E. Sahara Avenue, Suite 490-204
00013  *                              Las Vegas, Nevada 89104
00014  *
00015  * Licensed under The MIT License
00016  * Redistributions of files must retain the above copyright notice.
00017  *
00018  * @filesource
00019  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00020  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00021  * @package         cake
00022  * @subpackage      cake.cake.libs.controller.components
00023  * @since           CakePHP(tm) v 0.10.8.2156
00024  * @version         $Revision: 580 $
00025  * @modifiedby      $LastChangedBy: gwoo $
00026  * @lastmodified    $Date: 2008-07-01 09:45:49 -0500 (Tue, 01 Jul 2008) $
00027  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00028  */
00029 /**
00030  * Short description for file.
00031  *
00032  * Long description for file
00033  *
00034  * @package     cake
00035  * @subpackage  cake.cake.libs.controller.components
00036  */
00037 class SecurityComponent extends Object {
00038 /**
00039  * The controller method that will be called if this request is black-hole'd
00040  *
00041  * @var string
00042  * @access public
00043  */
00044     var $blackHoleCallback = null;
00045 /**
00046  * List of controller actions for which a POST request is required
00047  *
00048  * @var array
00049  * @access public
00050  * @see SecurityComponent::requirePost()
00051  */
00052     var $requirePost = array();
00053 /**
00054  * List of controller actions for which a GET request is required
00055  *
00056  * @var array
00057  * @access public
00058  * @see SecurityComponent::requireGet()
00059  */
00060     var $requireGet = array();
00061 /**
00062  * List of controller actions for which a PUT request is required
00063  *
00064  * @var array
00065  * @access public
00066  * @see SecurityComponent::requirePut()
00067  */
00068     var $requirePut = array();
00069 /**
00070  * List of controller actions for which a DELETE request is required
00071  *
00072  * @var array
00073  * @access public
00074  * @see SecurityComponent::requireDelete()
00075  */
00076     var $requireDelete = array();
00077 /**
00078  * List of actions that require an SSL-secured connection
00079  *
00080  * @var array
00081  * @access public
00082  * @see SecurityComponent::requireSecure()
00083  */
00084     var $requireSecure = array();
00085 /**
00086  * List of actions that require a valid authentication key
00087  *
00088  * @var array
00089  * @access public
00090  * @see SecurityComponent::requireAuth()
00091  */
00092     var $requireAuth = array();
00093 /**
00094  * List of actions that require an HTTP-authenticated login (basic or digest)
00095  *
00096  * @var array
00097  * @access public
00098  * @see SecurityComponent::requireLogin()
00099  */
00100     var $requireLogin = array();
00101 /**
00102  * Login options for SecurityComponent::requireLogin()
00103  *
00104  * @var array
00105  * @access public
00106  * @see SecurityComponent::requireLogin()
00107  */
00108     var $loginOptions = array('type' => '', 'prompt' => null);
00109 /**
00110  * An associative array of usernames/passwords used for HTTP-authenticated logins.
00111  * If using digest authentication, passwords should be MD5-hashed.
00112  *
00113  * @var array
00114  * @access public
00115  * @see SecurityComponent::requireLogin()
00116  */
00117     var $loginUsers = array();
00118 /**
00119  * Controllers from which actions of the current controller are allowed to receive
00120  * requests.
00121  *
00122  * @var array
00123  * @access public
00124  * @see SecurityComponent::requireAuth()
00125  */
00126     var $allowedControllers = array();
00127 /**
00128  * Actions from which actions of the current controller are allowed to receive
00129  * requests.
00130  *
00131  * @var array
00132  * @access public
00133  * @see SecurityComponent::requireAuth()
00134  */
00135     var $allowedActions = array();
00136 /**
00137  * Form fields to disable
00138  *
00139  * @var array
00140  * @access public
00141  */
00142     var $disabledFields = array();
00143 /**
00144  * Other components used by the Security component
00145  *
00146  * @var array
00147  * @access public
00148  */
00149     var $components = array('RequestHandler', 'Session');
00150 /**
00151  * Holds the current action of the controller
00152  *
00153  * @var string
00154  */
00155     var $__action = null;
00156 /**
00157  * Component startup. All security checking happens here.
00158  *
00159  * @param object $controller Instantiating controller
00160  * @access public
00161  */
00162     function startup(&$controller) {
00163         $this->__action = strtolower($controller->action);
00164         $this->__methodsRequired($controller);
00165         $this->__secureRequired($controller);
00166         $this->__authRequired($controller);
00167         $this->__loginRequired($controller);
00168 
00169         if ((!isset($controller->params['requested']) || $controller->params['requested'] != 1) && ($this->RequestHandler->isPost() || $this->RequestHandler->isPut())) {
00170             $this->__validatePost($controller);
00171         }
00172 
00173         $this->__generateToken($controller);
00174     }
00175 /**
00176  * Sets the actions that require a POST request, or empty for all actions
00177  *
00178  * @access public
00179  */
00180     function requirePost() {
00181         $args = func_get_args();
00182         $this->__requireMethod('Post', $args);
00183     }
00184 /**
00185  * Sets the actions that require a GET request, or empty for all actions
00186  *
00187  * @access public
00188  */
00189     function requireGet() {
00190         $args = func_get_args();
00191         $this->__requireMethod('Get', $args);
00192     }
00193 /**
00194  * Sets the actions that require a PUT request, or empty for all actions
00195  *
00196  * @access public
00197  */
00198     function requirePut() {
00199         $args = func_get_args();
00200         $this->__requireMethod('Put', $args);
00201     }
00202 /**
00203  * Sets the actions that require a DELETE request, or empty for all actions
00204  *
00205  * @access public
00206  */
00207     function requireDelete() {
00208         $args = func_get_args();
00209         $this->__requireMethod('Delete', $args);
00210     }
00211 /**
00212  * Sets the actions that require a request that is SSL-secured, or empty for all actions
00213  *
00214  * @access public
00215  */
00216     function requireSecure() {
00217         $args = func_get_args();
00218         $this->__requireMethod('Secure', $args);
00219     }
00220 /**
00221  * Sets the actions that require an authenticated request, or empty for all actions
00222  *
00223  * @access public
00224  */
00225     function requireAuth() {
00226         $args = func_get_args();
00227         $this->__requireMethod('Auth', $args);
00228     }
00229 /**
00230  * Sets the actions that require an HTTP-authenticated request, or empty for all actions
00231  *
00232  * @access public
00233  */
00234     function requireLogin() {
00235         $args = func_get_args();
00236         $base = $this->loginOptions;
00237 
00238         foreach ($args as $i => $arg) {
00239             if (is_array($arg)) {
00240                 $this->loginOptions = $arg;
00241                 unset($args[$i]);
00242             }
00243         }
00244         $this->loginOptions = array_merge($base, $this->loginOptions);
00245         $this->__requireMethod('Login', $args);
00246 
00247         if (isset($this->loginOptions['users'])) {
00248             $this->loginUsers =& $this->loginOptions['users'];
00249         }
00250     }
00251 /**
00252  * Attempts to validate the login credentials for an HTTP-authenticated request
00253  *
00254  * @param string $type Either 'basic', 'digest', or null. If null/empty, will try both.
00255  * @return mixed If successful, returns an array with login name and password, otherwise null.
00256  * @access public
00257  */
00258     function loginCredentials($type = null) {
00259         switch (strtolower($type)) {
00260             case 'basic':
00261                 $login = array('username' => env('PHP_AUTH_USER'), 'password' => env('PHP_AUTH_PW'));
00262                 if (!empty($login['username'])) {
00263                     return $login;
00264                 }
00265             break;
00266             case 'digest':
00267             default:
00268                 $digest = null;
00269 
00270                 if (version_compare(phpversion(), '5.1') != -1) {
00271                     $digest = env('PHP_AUTH_DIGEST');
00272                 } elseif (function_exists('apache_request_headers')) {
00273                     $headers = apache_request_headers();
00274                     if (isset($headers['Authorization']) && !empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) == 'Digest ') {
00275                         $digest = substr($headers['Authorization'], 7);
00276                     }
00277                 } else {
00278                     // Server doesn't support digest-auth headers
00279                     trigger_error(__('SecurityComponent::loginCredentials() - Server does not support digest authentication', true), E_USER_WARNING);
00280                 }
00281 
00282                 if (!empty($digest)) {
00283                     return $this->parseDigestAuthData($digest);
00284                 }
00285             break;
00286         }
00287         return null;
00288     }
00289 /**
00290  * Generates the text of an HTTP-authentication request header from an array of options..
00291  *
00292  * @param array $options Set of options for header
00293  * @return string HTTP-authentication request header
00294  * @access public
00295  */
00296     function loginRequest($options = array()) {
00297         $options = array_merge($this->loginOptions, $options);
00298         $this->__setLoginDefaults($options);
00299         $auth = 'WWW-Authenticate: ' . ucfirst($options['type']);
00300         $out = array('realm="' . $options['realm'] . '"');
00301 
00302         if (strtolower($options['type']) == 'digest') {
00303             $out[] = 'qop="auth"';
00304             $out[] = 'nonce="' . uniqid() . '"'; //str_replace('-', '', String::uuid())
00305             $out[] = 'opaque="' . md5($options['realm']).'"';
00306         }
00307 
00308         return $auth . ' ' . join(',', $out);
00309     }
00310 /**
00311  * Parses an HTTP digest authentication response, and returns an array of the data, or null on failure.
00312  *
00313  * @param string $digest Digest authentication response
00314  * @return array Digest authentication parameters
00315  * @access public
00316  */
00317     function parseDigestAuthData($digest) {
00318         if (substr($digest, 0, 7) == 'Digest ') {
00319             $digest = substr($digest, 7);
00320         }
00321         $keys = array();
00322         $match = array();
00323         $req = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
00324         preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $digest, $match, PREG_SET_ORDER);
00325 
00326         foreach ($match as $i) {
00327             $keys[$i[1]] = $i[3];
00328             unset($req[$i[1]]);
00329         }
00330 
00331         if (empty($req)) {
00332             return $keys;
00333         } else {
00334             return null;
00335         }
00336     }
00337 /**
00338  * Generates a hash to be compared with an HTTP digest-authenticated response
00339  *
00340  * @param array $data HTTP digest response data, as parsed by SecurityComponent::parseDigestAuthData()
00341  * @return string Digest authentication hash
00342  * @access public
00343  * @see SecurityComponent::parseDigestAuthData()
00344  */
00345     function generateDigestResponseHash($data) {
00346         return md5(
00347             md5($data['username'] . ':' . $this->loginOptions['realm'] . ':' . $this->loginUsers[$data['username']]) .
00348             ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' .
00349             md5(env('REQUEST_METHOD') . ':' . $data['uri'])
00350         );
00351     }
00352 /**
00353  * Black-hole an invalid request with a 404 error or custom callback. If SecurityComponent::$blackHoleCallback
00354  * is specified, it will use this callback by executing the method indicated in $error
00355  *
00356  * @param object $controller Instantiating controller
00357  * @param string $error Error method
00358  * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise
00359  * @access public
00360  * @see SecurityComponent::$blackHoleCallback
00361  */
00362     function blackHole(&$controller, $error = '') {
00363         $this->Session->del('_Token');
00364 
00365         if ($this->blackHoleCallback == null) {
00366             $code = 404;
00367             if ($error == 'login') {
00368                 $code = 401;
00369             }
00370             $controller->redirect(null, $code, true);
00371         } else {
00372             return $this->__callback($controller, $this->blackHoleCallback, array($error));
00373         }
00374     }
00375 /**
00376  * Sets the actions that require a $method HTTP request, or empty for all actions
00377  *
00378  * @param string $method The HTTP method to assign controller actions to
00379  * @param array $actions Controller actions to set the required HTTP method to.
00380  * @access private
00381  */
00382     function __requireMethod($method, $actions = array()) {
00383         $this->{'require' . $method} = ife(empty($actions), array('*'), $actions);
00384     }
00385 /**
00386  * Check if HTTP methods are required
00387  *
00388  * @param object $controller Instantiating controller
00389  * @return bool true if $method is required
00390  * @access private
00391  */
00392     function __methodsRequired(&$controller) {
00393         foreach (array('Post', 'Get', 'Put', 'Delete') as $method) {
00394             $property = 'require' . $method;
00395             if (is_array($this->$property) && !empty($this->$property)) {
00396                 $require = array_map('strtolower', $this->$property);
00397 
00398                 if (in_array($this->__action, $require) || $this->$property == array('*')) {
00399                     if (!$this->RequestHandler->{'is' . $method}()) {
00400                         if (!$this->blackHole($controller, strtolower($method))) {
00401                             return null;
00402                         }
00403                     }
00404                 }
00405             }
00406         }
00407         return true;
00408     }
00409 /**
00410  * Check if access requires secure connection
00411  *
00412  * @param object $controller Instantiating controller
00413  * @return bool true if secure connection required
00414  * @access private
00415  */
00416     function __secureRequired(&$controller) {
00417         if (is_array($this->requireSecure) && !empty($this->requireSecure)) {
00418             $requireSecure = array_map('strtolower', $this->requireSecure);
00419 
00420             if (in_array($this->__action, $requireSecure) || $this->requireSecure == array('*')) {
00421                 if (!$this->RequestHandler->isSSL()) {
00422                     if (!$this->blackHole($controller, 'secure')) {
00423                         return null;
00424                     }
00425                 }
00426             }
00427         }
00428         return true;
00429     }
00430 /**
00431  * Check if authentication is required
00432  *
00433  * @param object $controller Instantiating controller
00434  * @return bool true if authentication required
00435  * @access private
00436  */
00437     function __authRequired(&$controller) {
00438         if (is_array($this->requireAuth) && !empty($this->requireAuth) && !empty($controller->data)) {
00439             $requireAuth = array_map('strtolower', $this->requireAuth);
00440 
00441             if (in_array($this->__action, $requireAuth) || $this->requireAuth == array('*')) {
00442                 if (!isset($controller->data['__Token'] )) {
00443                     if (!$this->blackHole($controller, 'auth')) {
00444                         return null;
00445                     }
00446                 }
00447 
00448                 if ($this->Session->check('_Token')) {
00449                     $tData = unserialize($this->Session->read('_Token'));
00450 
00451                     if (!empty($tData['allowedControllers']) && !in_array($controller->params['controller'], $tData['allowedControllers']) || !empty($tData['allowedActions']) && !in_array($controller->params['action'], $tData['allowedActions'])) {
00452                         if (!$this->blackHole($controller, 'auth')) {
00453                             return null;
00454                         }
00455                     }
00456                 } else {
00457                     if (!$this->blackHole($controller, 'auth')) {
00458                         return null;
00459                     }
00460                 }
00461             }
00462         }
00463         return true;
00464     }
00465 /**
00466  * Check if login is required
00467  *
00468  * @param object $controller Instantiating controller
00469  * @return bool true if login is required
00470  * @access private
00471  */
00472     function __loginRequired(&$controller) {
00473         if (is_array($this->requireLogin) && !empty($this->requireLogin)) {
00474             $requireLogin = array_map('strtolower', $this->requireLogin);
00475 
00476             if (in_array($this->__action, $requireLogin) || $this->requireLogin == array('*')) {
00477                 $login = $this->loginCredentials($this->loginOptions['type']);
00478 
00479                 if ($login == null) {
00480                     // User hasn't been authenticated yet
00481                     header($this->loginRequest());
00482 
00483                     if (!empty($this->loginOptions['prompt'])) {
00484                         $this->__callback($controller, $this->loginOptions['prompt']);
00485                     } else {
00486                         $this->blackHole($controller, 'login');
00487                     }
00488                 } else {
00489                     if (isset($this->loginOptions['login'])) {
00490                         $this->__callback($controller, $this->loginOptions['login'], array($login));
00491                     } else {
00492                         if (strtolower($this->loginOptions['type']) == 'digest') {
00493                             // Do digest authentication
00494                             if ($login && isset($this->loginUsers[$login['username']])) {
00495                                 if ($login['response'] == $this->generateDigestResponseHash($login)) {
00496                                     return true;
00497                                 }
00498                             }
00499                             $this->blackHole($controller, 'login');
00500                         } else {
00501                             if (!(in_array($login['username'], array_keys($this->loginUsers)) && $this->loginUsers[$login['username']] == $login['password'])) {
00502                                 $this->blackHole($controller, 'login');
00503                             }
00504                         }
00505                     }
00506                 }
00507             }
00508         }
00509         return true;
00510     }
00511 /**
00512  * Validate submitted form
00513  *
00514  * @param object $controller Instantiating controller
00515  * @return bool true if submitted form is valid
00516  * @access private
00517  */
00518     function __validatePost(&$controller) {
00519         if (!empty($controller->data)) {
00520             if (!isset($controller->data['__Token'])) {
00521                 if (!$this->blackHole($controller, 'auth')) {
00522                     return null;
00523                 }
00524             }
00525             $token = $controller->data['__Token']['key'];
00526 
00527             if ($this->Session->check('_Token')) {
00528                 $tData = unserialize($this->Session->read('_Token'));
00529 
00530                 if ($tData['expires'] < time() || $tData['key'] !== $token) {
00531                     if (!$this->blackHole($controller, 'auth')) {
00532                         return null;
00533                     }
00534                 }
00535             }
00536 
00537             if (!isset($controller->data['__Token']['fields'])) {
00538                 if (!$this->blackHole($controller, 'auth')) {
00539                     return null;
00540                 }
00541             }
00542             $form = $controller->data['__Token']['fields'];
00543             $check = $controller->data;
00544             unset($check['__Token']['fields']);
00545 
00546             if (!empty($this->disabledFields)) {
00547                 foreach ($check as $model => $fields) {
00548                     foreach ($fields as $field => $value) {
00549                         $key[] = $model . '.' . $field;
00550                     }
00551                     unset($field);
00552                 }
00553 
00554                 foreach ($this->disabledFields as $value) {
00555                     $parts = preg_split('/\/|\./', $value);
00556 
00557                     if (count($parts) == 1) {
00558                         $key1[] = $controller->modelClass . '.' . $parts['0'];
00559                     } elseif (count($parts) == 2) {
00560                         $key1[] = $parts['0'] . '.' . $parts['1'];
00561                     }
00562                 }
00563 
00564                 foreach ($key1 as $value) {
00565                     if (in_array($value, $key)) {
00566                         $remove = explode('.', $value);
00567                         unset($check[$remove['0']][$remove['1']]);
00568                     } elseif (in_array('_' . $value, $key)) {
00569                         $remove = explode('.', $value);
00570                         $controller->data[$remove['0']][$remove['1']] = $controller->data['_' . $remove['0']][$remove['1']];
00571                         unset($check['_' . $remove['0']][$remove['1']]);
00572                     }
00573                 }
00574             }
00575             ksort($check);
00576             foreach ($check as $key => $value) {
00577                 $merge = array();
00578                 if ($key === '__Token') {
00579                     $field[$key] = $value;
00580                     continue;
00581                 }
00582                 $string = substr($key, 0, 1);
00583 
00584                 if ($string === '_') {
00585                     $newKey = substr($key, 1);
00586 
00587                     if (!isset($controller->data[$newKey])) {
00588                         $controller->data[$newKey] = array();
00589 
00590                         if (array_keys($controller->data[$key]) === array($newKey)) {
00591                             $field[$newKey] = array($newKey);
00592                         }
00593                     }
00594 
00595                     if (is_array($value)) {
00596                         $values = array_values($value);
00597                         $k = array_keys($value);
00598                         $count = count($k);
00599 
00600                         if (is_numeric($k[0])) {
00601                             for ($i = 0; $count > $i; $i++) {
00602                                 foreach ($values[$i] as $key2 => $value1) {
00603                                     if ($value1 === '0') {
00604                                         $field[$newKey][$i] = array_merge($field[$newKey][$i], array($key2));
00605                                     }
00606                                 }
00607                             }
00608                             $controller->data[$newKey] = Set::pushDiff($controller->data[$key], $controller->data[$newKey]);
00609                         }
00610 
00611                         for ($i = 0; $count > $i; $i++) {
00612                             $field[$key][$k[$i]] = $values[$i];
00613                         }
00614 
00615                         foreach ($k as $lookup) {
00616                             if (isset($controller->data[$newKey][$lookup])) {
00617                                 unset($controller->data[$key][$lookup]);
00618                             } elseif ($controller->data[$key][$lookup] === '0') {
00619                                 $merge[] = $lookup;
00620                             }
00621                         }
00622 
00623                         if (!is_numeric($k[0])) {
00624                             if (isset($field[$newKey])) {
00625                                 $field[$newKey] = array_merge($merge, $field[$newKey]);
00626                             } else {
00627                                 $field[$newKey] = $merge;
00628                             }
00629                             $controller->data[$newKey] = Set::pushDiff($controller->data[$key], $controller->data[$newKey]);
00630                         }
00631                         unset($controller->data[$key]);
00632                     }
00633                     continue;
00634                 }
00635                 if (is_array($value)) {
00636                     $keys = array_keys($value);
00637                 } else {
00638                     $keys = $value;
00639                 }
00640 
00641                 if (isset($field[$key])) {
00642                     $field[$key] = array_merge($field[$key], $keys);
00643                 } elseif (is_array($keys) && !empty($keys) && is_numeric($keys[0])) {
00644                     foreach ($value as $fields) {
00645                         $merge[] = array_keys($fields);
00646                     }
00647                     $field[$key] = $merge;
00648                 } else if (is_array($keys)) {
00649                     $field[$key] = $keys;
00650                 } else {
00651                     $field[] = $key;
00652                 }
00653             }
00654 
00655             foreach ($field as $key => $value) {
00656                 if ($key[0] != '_' && is_array($field[$key])) {
00657                     sort($field[$key]);
00658                 }
00659             }
00660             ksort($field, SORT_STRING);
00661 
00662             $check = urlencode(Security::hash(serialize($field) . Configure::read('Security.salt')));
00663             if ($form !== $check) {
00664                 if (!$this->blackHole($controller, 'auth')) {
00665                     return null;
00666                 }
00667             }
00668         }
00669         return true;
00670     }
00671 /**
00672  * Add authentication key for new form posts
00673  *
00674  * @param object $controller Instantiating controller
00675  * @return bool Success
00676  * @access private
00677  */
00678     function __generateToken(&$controller) {
00679         if (!isset($controller->params['requested']) || $controller->params['requested'] != 1) {
00680             $authKey = Security::generateAuthKey();
00681             $expires = strtotime('+' . Security::inactiveMins() . ' minutes');
00682             $token = array(
00683                 'key' => $authKey,
00684                 'expires' => $expires,
00685                 'allowedControllers' => $this->allowedControllers,
00686                 'allowedActions' => $this->allowedActions,
00687                 'disabledFields' => $this->disabledFields
00688             );
00689 
00690             if (!isset($controller->data)) {
00691                 $controller->data = array();
00692             }
00693 
00694             if ($this->Session->check('_Token')) {
00695                 $tData = unserialize($this->Session->read('_Token'));
00696                 if (isset($tData['expires']) && $tData['expires'] > time() && isset($tData['key'])) {
00697                     $token['key'] = $tData['key'];
00698                 }
00699             }
00700             $controller->params['_Token'] = $token;
00701             $this->Session->write('_Token', serialize($token));
00702         }
00703         return true;
00704     }
00705 /**
00706  * Sets the default login options for an HTTP-authenticated request
00707  *
00708  * @param array $options Default login options
00709  * @access private
00710  */
00711     function __setLoginDefaults(&$options) {
00712         $options = array_merge(array(
00713             'type' => 'basic',
00714             'realm' => env('SERVER_NAME'),
00715             'qop' => 'auth',
00716             'nonce' => String::uuid()
00717         ), array_filter($options));
00718         $options = array_merge(array('opaque' => md5($options['realm'])), $options);
00719     }
00720 /**
00721  * Calls a controller callback method
00722  *
00723  * @param object $controller Controller to run callback on
00724  * @param string $method Method to execute
00725  * @param array $params Parameters to send to method
00726  * @return mixed Controller callback method's response
00727  * @access private
00728  */
00729     function __callback(&$controller, $method, $params = array()) {
00730         if (is_callable(array($controller, $method))) {
00731             return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params);
00732         } else {
00733             // Debug::warning('Callback method ' . $method . ' in controller ' . get_class($controller)
00734             return null;
00735         }
00736     }
00737 }
00738 ?>