Plugin.php 7.5 KB

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