1<?php 2 3namespace Sabre\DAV\Auth; 4 5use Sabre\DAV\Exception\NotAuthenticated; 6use Sabre\DAV\Server; 7use Sabre\DAV\ServerPlugin; 8use Sabre\HTTP\RequestInterface; 9use Sabre\HTTP\ResponseInterface; 10 11/** 12 * This plugin provides Authentication for a WebDAV server. 13 * 14 * It works by providing a Auth\Backend class. Several examples of these 15 * classes can be found in the Backend directory. 16 * 17 * It's possible to provide more than one backend to this plugin. If more than 18 * one backend was provided, each backend will attempt to authenticate. Only if 19 * all backends fail, we throw a 401. 20 * 21 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 22 * @author Evert Pot (http://evertpot.com/) 23 * @license http://sabre.io/license/ Modified BSD License 24 */ 25class Plugin extends ServerPlugin { 26 27 /** 28 * By default this plugin will require that the user is authenticated, 29 * and refuse any access if the user is not authenticated. 30 * 31 * If this setting is set to false, we let the user through, whether they 32 * are authenticated or not. 33 * 34 * This is useful if you want to allow both authenticated and 35 * unauthenticated access to your server. 36 * 37 * @param bool 38 */ 39 public $autoRequireLogin = true; 40 41 /** 42 * authentication backends 43 */ 44 protected $backends; 45 46 /** 47 * The currently logged in principal. Will be `null` if nobody is currently 48 * logged in. 49 * 50 * @var string|null 51 */ 52 protected $currentPrincipal; 53 54 /** 55 * Creates the authentication plugin 56 * 57 * @param Backend\BackendInterface $authBackend 58 */ 59 function __construct(Backend\BackendInterface $authBackend = null) { 60 61 if (!is_null($authBackend)) { 62 $this->addBackend($authBackend); 63 } 64 65 } 66 67 /** 68 * Adds an authentication backend to the plugin. 69 * 70 * @param Backend\BackendInterface $authBackend 71 * @return void 72 */ 73 function addBackend(Backend\BackendInterface $authBackend) { 74 75 $this->backends[] = $authBackend; 76 77 } 78 79 /** 80 * Initializes the plugin. This function is automatically called by the server 81 * 82 * @param Server $server 83 * @return void 84 */ 85 function initialize(Server $server) { 86 87 $server->on('beforeMethod', [$this, 'beforeMethod'], 10); 88 89 } 90 91 /** 92 * Returns a plugin name. 93 * 94 * Using this name other plugins will be able to access other plugins 95 * using DAV\Server::getPlugin 96 * 97 * @return string 98 */ 99 function getPluginName() { 100 101 return 'auth'; 102 103 } 104 105 /** 106 * Returns the currently logged-in principal. 107 * 108 * This will return a string such as: 109 * 110 * principals/username 111 * principals/users/username 112 * 113 * This method will return null if nobody is logged in. 114 * 115 * @return string|null 116 */ 117 function getCurrentPrincipal() { 118 119 return $this->currentPrincipal; 120 121 } 122 123 /** 124 * This method is called before any HTTP method and forces users to be authenticated 125 * 126 * @param RequestInterface $request 127 * @param ResponseInterface $response 128 * @return bool 129 */ 130 function beforeMethod(RequestInterface $request, ResponseInterface $response) { 131 132 if ($this->currentPrincipal) { 133 134 // We already have authentication information. This means that the 135 // event has already fired earlier, and is now likely fired for a 136 // sub-request. 137 // 138 // We don't want to authenticate users twice, so we simply don't do 139 // anything here. See Issue #700 for additional reasoning. 140 // 141 // This is not a perfect solution, but will be fixed once the 142 // "currently authenticated principal" is information that's not 143 // not associated with the plugin, but rather per-request. 144 // 145 // See issue #580 for more information about that. 146 return; 147 148 } 149 150 $authResult = $this->check($request, $response); 151 152 if ($authResult[0]) { 153 // Auth was successful 154 $this->currentPrincipal = $authResult[1]; 155 $this->loginFailedReasons = null; 156 return; 157 } 158 159 160 161 // If we got here, it means that no authentication backend was 162 // successful in authenticating the user. 163 $this->currentPrincipal = null; 164 $this->loginFailedReasons = $authResult[1]; 165 166 if ($this->autoRequireLogin) { 167 $this->challenge($request, $response); 168 throw new NotAuthenticated(implode(', ', $authResult[1])); 169 } 170 171 } 172 173 /** 174 * Checks authentication credentials, and logs the user in if possible. 175 * 176 * This method returns an array. The first item in the array is a boolean 177 * indicating if login was successful. 178 * 179 * If login was successful, the second item in the array will contain the 180 * current principal url/path of the logged in user. 181 * 182 * If login was not successful, the second item in the array will contain a 183 * an array with strings. The strings are a list of reasons why login was 184 * unsuccessful. For every auth backend there will be one reason, so usually 185 * there's just one. 186 * 187 * @param RequestInterface $request 188 * @param ResponseInterface $response 189 * @return array 190 */ 191 function check(RequestInterface $request, ResponseInterface $response) { 192 193 if (!$this->backends) { 194 throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.'); 195 } 196 $reasons = []; 197 foreach ($this->backends as $backend) { 198 199 $result = $backend->check( 200 $request, 201 $response 202 ); 203 204 if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) { 205 throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.'); 206 } 207 208 if ($result[0]) { 209 $this->currentPrincipal = $result[1]; 210 // Exit early 211 return [true, $result[1]]; 212 } 213 $reasons[] = $result[1]; 214 215 } 216 217 return [false, $reasons]; 218 219 } 220 221 /** 222 * This method sends authentication challenges to the user. 223 * 224 * This method will for example cause a HTTP Basic backend to set a 225 * WWW-Authorization header, indicating to the client that it should 226 * authenticate. 227 * 228 * @param RequestInterface $request 229 * @param ResponseInterface $response 230 * @return array 231 */ 232 function challenge(RequestInterface $request, ResponseInterface $response) { 233 234 foreach ($this->backends as $backend) { 235 $backend->challenge($request, $response); 236 } 237 238 } 239 240 /** 241 * List of reasons why login failed for the last login operation. 242 * 243 * @var string[]|null 244 */ 245 protected $loginFailedReasons; 246 247 /** 248 * Returns a list of reasons why login was unsuccessful. 249 * 250 * This method will return the login failed reasons for the last login 251 * operation. One for each auth backend. 252 * 253 * This method returns null if the last authentication attempt was 254 * successful, or if there was no authentication attempt yet. 255 * 256 * @return string[]|null 257 */ 258 function getLoginFailedReasons() { 259 260 return $this->loginFailedReasons; 261 262 } 263 264 /** 265 * Returns a bunch of meta-data about the plugin. 266 * 267 * Providing this information is optional, and is mainly displayed by the 268 * Browser plugin. 269 * 270 * The description key in the returned array may contain html and will not 271 * be sanitized. 272 * 273 * @return array 274 */ 275 function getPluginInfo() { 276 277 return [ 278 'name' => $this->getPluginName(), 279 'description' => 'Generic authentication plugin', 280 'link' => 'http://sabre.io/dav/authentication/', 281 ]; 282 283 } 284 285} 286