Skip to content

Commit

Permalink
Merge pull request #582 from fruux/multi-auth
Browse files Browse the repository at this point in the history
Authentication refactoring
  • Loading branch information
evert committed Dec 8, 2014
2 parents 039cbdb + 6d1dfa3 commit 231d0a8
Show file tree
Hide file tree
Showing 17 changed files with 647 additions and 307 deletions.
7 changes: 7 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ ChangeLog
* The browser plugin now has a new page with information about your sabredav
server, and shows information about every plugin that's loaded in the
system.
* #191: The Authentication system can now support multiple authentication
backends.
* Removed: all `$tableName` arguments from every PDO backend. This was already
deprecated, but has now been fully removed. All of these have been replaced
with public properties.
Expand All @@ -26,6 +28,11 @@ ChangeLog
`application/octet-stream` if a better content-type could not be determined.
* #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to
specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`.
* #582: Authentication backend interface changed to be stateless. If you
implemented your own authentication backend, make sure you upgrade your class
to the latest API!
* #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use
`Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead.


2.1.2 (2014-??-??)
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"php": ">=5.4.1",
"sabre/vobject": "~3.3.4",
"sabre/event" : "~2.0.0",
"sabre/http" : "~3.0.0",
"sabre/http" : "dev-master",
"ext-dom": "*",
"ext-pcre": "*",
"ext-spl": "*",
Expand Down
120 changes: 90 additions & 30 deletions lib/DAV/Auth/Backend/AbstractBasic.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace Sabre\DAV\Auth\Backend;

use Sabre\DAV;
use Sabre\HTTP;
use
Sabre\DAV,
Sabre\HTTP,
Sabre\HTTP\RequestInterface,
Sabre\HTTP\ResponseInterface;

/**
* HTTP Basic authentication backend class
Expand All @@ -20,11 +23,21 @@
abstract class AbstractBasic implements BackendInterface {

/**
* This variable holds the currently logged in username.
* Authentication Realm.
*
* @var string|null
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'sabre/dav';

/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
protected $currentUser;
protected $principalPrefix = 'principals/';

/**
* Validates a username and password
Expand All @@ -39,46 +52,93 @@ abstract class AbstractBasic implements BackendInterface {
abstract protected function validateUserPass($username, $password);

/**
* Returns information about the currently logged in username.
*
* If nobody is currently logged in, this method should return null.
* Sets the authentication realm for this backend.
*
* @return string|null
* @param string $realm
* @return void
*/
function getCurrentUser() {
return $this->currentUser;
}
function setRealm($realm) {

$this->realm = $realm;

}

/**
* Authenticates the user based on the current request.
* When this method is called, the backend must check if authentication was
* successful.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
* The returned value must be one of the following
*
* @param DAV\Server $server
* @param string $realm
* @throws DAV\Exception\NotAuthenticated
* @return bool
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
function authenticate(DAV\Server $server, $realm) {
function check(RequestInterface $request, ResponseInterface $response) {

$auth = new HTTP\Auth\Basic($realm, $server->httpRequest, $server->httpResponse);
$userpass = $auth->getCredentials($server->httpRequest);
$auth = new HTTP\Auth\Basic(
$this->realm,
$request,
$response
);

$userpass = $auth->getCredentials($request);
if (!$userpass) {
$auth->requireLogin();
throw new DAV\Exception\NotAuthenticated('No basic authentication headers were found');
return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is mis-configured"];
}

// Authenticates the user
if (!$this->validateUserPass($userpass[0],$userpass[1])) {
$auth->requireLogin();
throw new DAV\Exception\NotAuthenticated('Username or password does not match');
return [false, "Username or password was incorrect"];
}
$this->currentUser = $userpass[0];
return true;
return [true, $this->principalPrefix . $userpass[0]];

}

/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the oppurtunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*
* @return void
*/
function requireAuth(RequestInterface $request, ResponseInterface $response) {

$auth = new HTTP\Auth\Basic(
$this->realm,
$request,
$response
);
$auth->requireLogin();

}

}

119 changes: 92 additions & 27 deletions lib/DAV/Auth/Backend/AbstractDigest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use
Sabre\HTTP,
Sabre\DAV;
Sabre\DAV,
Sabre\HTTP\RequestInterface,
Sabre\HTTP\ResponseInterface;

/**
* HTTP Digest authentication backend class
Expand All @@ -20,11 +22,37 @@
abstract class AbstractDigest implements BackendInterface {

/**
* This variable holds the currently logged in username.
* Authentication Realm.
*
* @var array|null
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'SabreDAV';

/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
protected $principalPrefix = 'principals/';

/**
* Sets the authentication realm for this backend.
*
* Be aware that for Digest authentication, the realm influences the digest
* hash. Choose the realm wisely, because if you change it later, all the
* existing hashes will break and nobody can authenticate.
*
* @param string $realm
* @return void
*/
protected $currentUser;
function setRealm($realm) {

$this->realm = $realm;

}

/**
* Returns a users digest hash based on the username and realm.
Expand All @@ -38,58 +66,95 @@ abstract class AbstractDigest implements BackendInterface {
abstract function getDigestHash($realm, $username);

/**
* Authenticates the user based on the current request.
* When this method is called, the backend must check if authentication was
* successful.
*
* If authentication is successful, true must be returned.
* If authentication fails, an exception must be thrown.
* The returned value must be one of the following
*
* @param DAV\Server $server
* @param string $realm
* @throws DAV\Exception\NotAuthenticated
* @return bool
* [true, "principals/username"]
* [false, "reason for failure"]
*
* If authentication was successful, it's expected that the authentication
* backend returns a so-called principal url.
*
* Examples of a principal url:
*
* principals/admin
* principals/user1
* principals/users/joe
* principals/uid/123457
*
* If you don't use WebDAV ACL (RFC3744) we recommend that you simply
* return a string such as:
*
* principals/users/[username]
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
function authenticate(DAV\Server $server, $realm) {
function check(RequestInterface $request, ResponseInterface $response) {

$digest = new HTTP\Auth\Digest($realm, $server->httpRequest, $server->httpResponse);
$digest = new HTTP\Auth\Digest(
$this->realm,
$request,
$response
);
$digest->init();

$username = $digest->getUsername();

// No username was given
if (!$username) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('No digest authentication headers were found');
return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is mis-configured"];
}

$hash = $this->getDigestHash($realm, $username);
$hash = $this->getDigestHash($this->realm, $username);
// If this was false, the user account didn't exist
if ($hash===false || is_null($hash)) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('The supplied username was not on file');
return [false, "Username or password was incorrect"];
}
if (!is_string($hash)) {
throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
}

// If this was false, the password or part of the hash was incorrect.
if (!$digest->validateA1($hash)) {
$digest->requireLogin();
throw new DAV\Exception\NotAuthenticated('Incorrect username');
return [false, "Username or password was incorrect"];
}

$this->currentUser = $username;
return true;
return [true, $this->principalPrefix . $username];

}

/**
* Returns the currently logged in username.
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* @return string|null
* This gives you the oppurtunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Basic Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*
* @return void
*/
function getCurrentUser() {

return $this->currentUser;
function requireAuth(RequestInterface $request, ResponseInterface $response) {

$auth = new HTTP\Auth\Digest(
$this->realm,
$request,
$response
);
$auth->init();
$auth->requireLogin();

}

Expand Down
Loading

0 comments on commit 231d0a8

Please sign in to comment.