Refactoring AuthComponent and implementing digest authentication in SecurityComponent

git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5745 3807eeeb-6ff5-0310-8944-8be069107fe0
This commit is contained in:
nate 2007-10-09 21:00:32 +00:00
parent 736ab28b0b
commit 61c06ae94a
3 changed files with 114 additions and 65 deletions

View file

@ -54,6 +54,13 @@ class AuthComponent extends Object {
* @access public
*/
var $components = array('Session', 'RequestHandler');
/**
* A reference to the object used for authentication
*
* @var object
* @access public
*/
var $authenticate = null;
/**
* The name of the component to use for Authorization or set this to
* 'controller' will validate against Controller::isAuthorized()
@ -62,7 +69,7 @@ class AuthComponent extends Object {
* array('model'=> 'name'); will validate mapActions against model $name::isAuthorize(user, controller, mapAction)
* 'object' will validate Controller::action against object::isAuthorized(user, controller, action)
*
* @var string
* @var mixed
* @access public
*/
var $authorize = false;
@ -97,13 +104,6 @@ class AuthComponent extends Object {
* @access public
*/
var $fields = array('username' => 'username', 'password' => 'password');
/**
* the hash function to use, options: sha1, sha256, md5
*
* @var string
* @access public
*/
var $hash = 'sha1';
/**
* The session key name where the record of the current user is stored. If
* unspecified, it will be "Auth.{$userModel name}".
@ -262,16 +262,16 @@ class AuthComponent extends Object {
return;
}
if ($this->allowedActions == array('*') || in_array($controller->action, $this->allowedActions)) {
return false;
}
if (!$this->__setDefaults()) {
return false;
}
$this->data = $controller->data = $this->hashPasswords($controller->data);
if ($this->allowedActions == array('*') || in_array($controller->action, $this->allowedActions)) {
return false;
}
if (!isset($controller->params['url']['url'])) {
$url = '';
} else {
@ -774,6 +774,10 @@ class AuthComponent extends Object {
* @return array
*/
function hashPasswords($data) {
if (is_object($this->authenticate) && method_exists($this->authenticate, 'hashPasswords')) {
return $this->authenticate->hashPasswords($data);
}
if (isset($data[$this->userModel])) {
if (!empty($data[$this->userModel][$this->fields['username']]) && !empty($data[$this->userModel][$this->fields['password']])) {
$data[$this->userModel][$this->fields['password']] = $this->password($data[$this->userModel][$this->fields['password']]);
@ -790,7 +794,7 @@ class AuthComponent extends Object {
* @return string
*/
function password($password) {
return Security::hash(CAKE_SESSION_STRING . $password, $this->hash);
return Security::hash(CAKE_SESSION_STRING . $password);
}
/**
* Component shutdown. If user is logged in, wipe out redirect.

View file

@ -35,13 +35,6 @@
* @subpackage cake.cake.libs.controller.components
*/
class SecurityComponent extends Object {
/**
* Holds an instance of the core Security object
*
* @var object Security
* @access public
*/
var $Security = null;
/**
* The controller method that will be called if this request is black-hole'd
*
@ -88,7 +81,7 @@ class SecurityComponent extends Object {
* @access public
* @see SecurityComponent::requireLogin()
*/
var $loginOptions = array('type' => '');
var $loginOptions = array('type' => '', 'prompt' => null);
/**
* An associative array of usernames/passwords used for HTTP-authenticated logins.
* If using digest authentication, passwords should be MD5-hashed.
@ -127,12 +120,6 @@ class SecurityComponent extends Object {
* @access public
*/
var $components = array('RequestHandler', 'Session');
/**
* Security class initialization
*/
function initialize(&$controller) {
$this->Security =& Security::getInstance();
}
/**
* Component startup. All security checking happens here.
*
@ -196,6 +183,8 @@ class SecurityComponent extends Object {
*/
function requireLogin() {
$args = func_get_args();
$base = $this->loginOptions;
foreach ($args as $arg) {
if (is_array($arg)) {
$this->loginOptions = $arg;
@ -203,6 +192,7 @@ class SecurityComponent extends Object {
$this->requireLogin[] = $arg;
}
}
$this->loginOptions = am($base, $this->loginOptions);
if (empty($this->requireLogin)) {
$this->requireLogin = array('*');
@ -213,41 +203,40 @@ class SecurityComponent extends Object {
}
}
/**
* Gets the login credentials for an HTTP-authenticated request
* Attempts to validate the login credentials for an HTTP-authenticated request
*
* @param string $type Either 'basic', 'digest', or null. If null/empty, will try both.
* @return mixed If successful, returns an array with login name and password, otherwise null.
* @access public
*/
function loginCredentials($type = null) {
if (empty($type) || low($type) == 'basic') {
$login = array('username' => env('PHP_AUTH_USER'), 'password' => env('PHP_AUTH_PW'));
if ($login['username'] != null) {
return $login;
}
}
if ($type == '' || low($type) == 'digest') {
$digest = null;
if (version_compare(phpversion(), '5.1') != -1) {
$digest = env('PHP_AUTH_DIGEST');
} elseif (function_exists('apache_request_headers')) {
$headers = apache_request_headers();
if (isset($headers['Authorization']) && !empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) == 'Digest ') {
$digest = substr($headers['Authorization'], 7);
switch (low($type)) {
case 'basic':
$login = array('username' => env('PHP_AUTH_USER'), 'password' => env('PHP_AUTH_PW'));
if (!empty($login['username'])) {
return $login;
}
} else {
// Server doesn't support digest-auth headers
trigger_error(__('SecurityComponent::loginCredentials() - Server does not support digest authentication', true), E_USER_WARNING);
return null;
}
break;
case 'digest':
default:
$digest = null;
if ($digest == null) {
return null;
}
$data = $this->parseDigestAuthData($digest);
if (version_compare(phpversion(), '5.1') != -1) {
$digest = env('PHP_AUTH_DIGEST');
} elseif (function_exists('apache_request_headers')) {
$headers = apache_request_headers();
if (isset($headers['Authorization']) && !empty($headers['Authorization']) && substr($headers['Authorization'], 0, 7) == 'Digest ') {
$digest = substr($headers['Authorization'], 7);
}
} else {
// Server doesn't support digest-auth headers
trigger_error(__('SecurityComponent::loginCredentials() - Server does not support digest authentication', true), E_USER_WARNING);
}
if (!empty($digest)) {
return $this->parseDigestAuthData($digest);
}
break;
}
return null;
}
@ -261,8 +250,16 @@ class SecurityComponent extends Object {
function loginRequest($options = array()) {
$options = am($this->loginOptions, $options);
$this->__setLoginDefaults($options);
$data = 'WWW-Authenticate: ' . ucfirst($options['type']) . ' realm="' . $options['realm'] . '"';
return $data;
$auth = 'WWW-Authenticate: ' . ucfirst($options['type']);
$out = array('realm="' . $options['realm'] . '"');
if (low($options['type']) == 'digest') {
$out[] = 'qop="auth"';
$out[] = 'nonce="' . uniqid() . '"'; //str_replace('-', '', String::uuid())
$out[] = 'opaque="' . md5($options['realm']).'"';
}
return $auth . ' ' . join(',', $out);
}
/**
* Parses an HTTP digest authentication response, and returns an array of the data, or null on failure.
@ -291,6 +288,21 @@ class SecurityComponent extends Object {
return null;
}
}
/**
* Generates a hash to be compared with an HTTP digest-authenticated response
*
* @param array $data HTTP digest response data, as parsed by SecurityComponent::parseDigestAuthData()
* @return string Digest authentication hash
* @access public
* @see SecurityComponent::parseDigestAuthData()
*/
function generateDigestResponseHash($data) {
return md5(
md5($data['username'] . ':' . $this->loginOptions['realm'] . ':' . $this->loginUsers[$data['username']]) .
':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' .
md5(env('REQUEST_METHOD') . ':' . $data['uri'])
);
}
/**
* Black-hole an invalid request with a 404 error or custom callback
*
@ -398,7 +410,7 @@ class SecurityComponent extends Object {
// User hasn't been authenticated yet
header($this->loginRequest());
if (isset($this->loginOptions['prompt'])) {
if (!empty($this->loginOptions['prompt'])) {
$this->__callback($controller, $this->loginOptions['prompt']);
} else {
$this->blackHole($controller, 'login');
@ -409,6 +421,12 @@ class SecurityComponent extends Object {
} else {
if (low($this->loginOptions['type']) == 'digest') {
// Do digest authentication
if ($login && isset($this->loginUsers[$login['username']])) {
if ($login['response'] == $this->generateDigestResponseHash($login)) {
return true;
}
}
$this->blackHole($controller, 'login');
} else {
if (!(in_array($login['username'], array_keys($this->loginUsers)) && $this->loginUsers[$login['username']] == $login['password'])) {
$this->blackHole($controller, 'login');
@ -579,11 +597,12 @@ class SecurityComponent extends Object {
* @access private
*/
function __setLoginDefaults(&$options) {
$options = am(array('type' => 'basic',
'realm' => env('SERVER_NAME'),
'qop' => 'auth',
'nonce' => String::uuid()),
array_filter($options));
$options = am(array(
'type' => 'basic',
'realm' => env('SERVER_NAME'),
'qop' => 'auth',
'nonce' => String::uuid()
), array_filter($options));
$options = am(array('opaque' => md5($options['realm'])), $options);
}
/**

View file

@ -34,7 +34,15 @@
* @package cake
* @subpackage cake.cake.libs
*/
class Security extends Object{
class Security extends Object {
/**
* Default hash method
*
* @var string
* @access public
*/
var $hashType = null;
/**
* Singleton implementation to get object instance.
*
@ -106,10 +114,14 @@ class Security extends Object{
* @access public
* @static
*/
function hash($string, $type = 'sha1') {
function hash($string, $type = null) {
$_this =& Security::getInstance();
if (empty($type)) {
$type = $_this->hashType;
}
$type = strtolower($type);
if ($type == 'sha1') {
if ($type == 'sha1' || $type == null) {
if (function_exists('sha1')) {
$return = sha1($string);
return $return;
@ -132,6 +144,20 @@ class Security extends Object{
return $return;
}
}
/**
* Sets the default hash method for the Security object. This affects all objects using
* Security::hash().
*
* @param string $hash Method to use (sha1/sha256/md5)
* @return void
* @access public
* @static
* @see Security::hash()
*/
function setHash($hash) {
$_this =& Security::getInstance();
$_this->hashType = $hash;
}
/**
* Encripts/Decrypts a text using the given key.
*