1<?php 2 3namespace Sabre\DAV\Auth\Backend; 4 5use Sabre\DAV; 6use Sabre\HTTP; 7use Sabre\HTTP\RequestInterface; 8use Sabre\HTTP\ResponseInterface; 9 10/** 11 * HTTP Digest authentication backend class 12 * 13 * This class can be used by authentication objects wishing to use HTTP Digest 14 * Most of the digest logic is handled, implementors just need to worry about 15 * the getDigestHash method 16 * 17 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 18 * @author Evert Pot (http://evertpot.com/) 19 * @license http://sabre.io/license/ Modified BSD License 20 */ 21abstract class AbstractDigest implements BackendInterface { 22 23 /** 24 * Authentication Realm. 25 * 26 * The realm is often displayed by browser clients when showing the 27 * authentication dialog. 28 * 29 * @var string 30 */ 31 protected $realm = 'SabreDAV'; 32 33 /** 34 * This is the prefix that will be used to generate principal urls. 35 * 36 * @var string 37 */ 38 protected $principalPrefix = 'principals/'; 39 40 /** 41 * Sets the authentication realm for this backend. 42 * 43 * Be aware that for Digest authentication, the realm influences the digest 44 * hash. Choose the realm wisely, because if you change it later, all the 45 * existing hashes will break and nobody can authenticate. 46 * 47 * @param string $realm 48 * @return void 49 */ 50 function setRealm($realm) { 51 52 $this->realm = $realm; 53 54 } 55 56 /** 57 * Returns a users digest hash based on the username and realm. 58 * 59 * If the user was not known, null must be returned. 60 * 61 * @param string $realm 62 * @param string $username 63 * @return string|null 64 */ 65 abstract function getDigestHash($realm, $username); 66 67 /** 68 * When this method is called, the backend must check if authentication was 69 * successful. 70 * 71 * The returned value must be one of the following 72 * 73 * [true, "principals/username"] 74 * [false, "reason for failure"] 75 * 76 * If authentication was successful, it's expected that the authentication 77 * backend returns a so-called principal url. 78 * 79 * Examples of a principal url: 80 * 81 * principals/admin 82 * principals/user1 83 * principals/users/joe 84 * principals/uid/123457 85 * 86 * If you don't use WebDAV ACL (RFC3744) we recommend that you simply 87 * return a string such as: 88 * 89 * principals/users/[username] 90 * 91 * @param RequestInterface $request 92 * @param ResponseInterface $response 93 * @return array 94 */ 95 function check(RequestInterface $request, ResponseInterface $response) { 96 97 $digest = new HTTP\Auth\Digest( 98 $this->realm, 99 $request, 100 $response 101 ); 102 $digest->init(); 103 104 $username = $digest->getUsername(); 105 106 // No username was given 107 if (!$username) { 108 return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"]; 109 } 110 111 $hash = $this->getDigestHash($this->realm, $username); 112 // If this was false, the user account didn't exist 113 if ($hash === false || is_null($hash)) { 114 return [false, "Username or password was incorrect"]; 115 } 116 if (!is_string($hash)) { 117 throw new DAV\Exception('The returned value from getDigestHash must be a string or null'); 118 } 119 120 // If this was false, the password or part of the hash was incorrect. 121 if (!$digest->validateA1($hash)) { 122 return [false, "Username or password was incorrect"]; 123 } 124 125 return [true, $this->principalPrefix . $username]; 126 127 } 128 129 /** 130 * This method is called when a user could not be authenticated, and 131 * authentication was required for the current request. 132 * 133 * This gives you the opportunity to set authentication headers. The 401 134 * status code will already be set. 135 * 136 * In this case of Basic Auth, this would for example mean that the 137 * following header needs to be set: 138 * 139 * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); 140 * 141 * Keep in mind that in the case of multiple authentication backends, other 142 * WWW-Authenticate headers may already have been set, and you'll want to 143 * append your own WWW-Authenticate header instead of overwriting the 144 * existing one. 145 * 146 * @param RequestInterface $request 147 * @param ResponseInterface $response 148 * @return void 149 */ 150 function challenge(RequestInterface $request, ResponseInterface $response) { 151 152 $auth = new HTTP\Auth\Digest( 153 $this->realm, 154 $request, 155 $response 156 ); 157 $auth->init(); 158 159 $oldStatus = $response->getStatus() ?: 200; 160 $auth->requireLogin(); 161 162 // Preventing the digest utility from modifying the http status code, 163 // this should be handled by the main plugin. 164 $response->setStatus($oldStatus); 165 166 } 167 168} 169