c72e3f0c0168e24e49d9a694257d079d85910d09
[yaffs-website] / web / core / modules / basic_auth / src / Authentication / Provider / BasicAuth.php
1 <?php
2
3 namespace Drupal\basic_auth\Authentication\Provider;
4
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Authentication\AuthenticationProviderInterface;
7 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
8 use Drupal\Core\Config\ConfigFactoryInterface;
9 use Drupal\Core\Entity\EntityManagerInterface;
10 use Drupal\Core\Flood\FloodInterface;
11 use Drupal\user\UserAuthInterface;
12 use Symfony\Component\HttpFoundation\Request;
13 use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
14
15 /**
16  * HTTP Basic authentication provider.
17  */
18 class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
19
20   /**
21    * The config factory.
22    *
23    * @var \Drupal\Core\Config\ConfigFactoryInterface
24    */
25   protected $configFactory;
26
27   /**
28    * The user auth service.
29    *
30    * @var \Drupal\user\UserAuthInterface
31    */
32   protected $userAuth;
33
34   /**
35    * The flood service.
36    *
37    * @var \Drupal\Core\Flood\FloodInterface
38    */
39   protected $flood;
40
41   /**
42    * The entity manager.
43    *
44    * @var \Drupal\Core\Entity\EntityManagerInterface
45    */
46   protected $entityManager;
47
48   /**
49    * Constructs a HTTP basic authentication provider object.
50    *
51    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
52    *   The config factory.
53    * @param \Drupal\user\UserAuthInterface $user_auth
54    *   The user authentication service.
55    * @param \Drupal\Core\Flood\FloodInterface $flood
56    *   The flood service.
57    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
58    *   The entity manager service.
59    */
60   public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityManagerInterface $entity_manager) {
61     $this->configFactory = $config_factory;
62     $this->userAuth = $user_auth;
63     $this->flood = $flood;
64     $this->entityManager = $entity_manager;
65   }
66
67   /**
68    * {@inheritdoc}
69    */
70   public function applies(Request $request) {
71     $username = $request->headers->get('PHP_AUTH_USER');
72     $password = $request->headers->get('PHP_AUTH_PW');
73     return isset($username) && isset($password);
74   }
75
76   /**
77    * {@inheritdoc}
78    */
79   public function authenticate(Request $request) {
80     $flood_config = $this->configFactory->get('user.flood');
81     $username = $request->headers->get('PHP_AUTH_USER');
82     $password = $request->headers->get('PHP_AUTH_PW');
83     // Flood protection: this is very similar to the user login form code.
84     // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
85     // Do not allow any login from the current user's IP if the limit has been
86     // reached. Default is 50 failed attempts allowed in one hour. This is
87     // independent of the per-user limit to catch attempts from one IP to log
88     // in to many different user accounts.  We have a reasonably high limit
89     // since there may be only one apparent IP for all users at an institution.
90     if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
91       $accounts = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]);
92       $account = reset($accounts);
93       if ($account) {
94         if ($flood_config->get('uid_only')) {
95           // Register flood events based on the uid only, so they apply for any
96           // IP address. This is the most secure option.
97           $identifier = $account->id();
98         }
99         else {
100           // The default identifier is a combination of uid and IP address. This
101           // is less secure but more resistant to denial-of-service attacks that
102           // could lock out all users with public user names.
103           $identifier = $account->id() . '-' . $request->getClientIP();
104         }
105         // Don't allow login if the limit for this user has been reached.
106         // Default is to allow 5 failed attempts every 6 hours.
107         if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
108           $uid = $this->userAuth->authenticate($username, $password);
109           if ($uid) {
110             $this->flood->clear('basic_auth.failed_login_user', $identifier);
111             return $this->entityManager->getStorage('user')->load($uid);
112           }
113           else {
114             // Register a per-user failed login event.
115             $this->flood->register('basic_auth.failed_login_user', $flood_config->get('user_window'), $identifier);
116           }
117         }
118       }
119     }
120     // Always register an IP-based failed login event.
121     $this->flood->register('basic_auth.failed_login_ip', $flood_config->get('ip_window'));
122     return [];
123   }
124
125   /**
126    * {@inheritdoc}
127    */
128   public function challengeException(Request $request, \Exception $previous) {
129     $site_name = $this->configFactory->get('system.site')->get('name');
130     $challenge = SafeMarkup::format('Basic realm="@realm"', [
131       '@realm' => !empty($site_name) ? $site_name : 'Access restricted',
132     ]);
133     return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
134   }
135
136 }