Version 1
[yaffs-website] / web / core / modules / basic_auth / src / Authentication / Provider / BasicAuth.php
diff --git a/web/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/web/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
new file mode 100644 (file)
index 0000000..c72e3f0
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\basic_auth\Authentication\Provider;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Flood\FloodInterface;
+use Drupal\user\UserAuthInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
+
+/**
+ * HTTP Basic authentication provider.
+ */
+class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The user auth service.
+   *
+   * @var \Drupal\user\UserAuthInterface
+   */
+  protected $userAuth;
+
+  /**
+   * The flood service.
+   *
+   * @var \Drupal\Core\Flood\FloodInterface
+   */
+  protected $flood;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a HTTP basic authentication provider object.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param \Drupal\user\UserAuthInterface $user_auth
+   *   The user authentication service.
+   * @param \Drupal\Core\Flood\FloodInterface $flood
+   *   The flood service.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager service.
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityManagerInterface $entity_manager) {
+    $this->configFactory = $config_factory;
+    $this->userAuth = $user_auth;
+    $this->flood = $flood;
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    $username = $request->headers->get('PHP_AUTH_USER');
+    $password = $request->headers->get('PHP_AUTH_PW');
+    return isset($username) && isset($password);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function authenticate(Request $request) {
+    $flood_config = $this->configFactory->get('user.flood');
+    $username = $request->headers->get('PHP_AUTH_USER');
+    $password = $request->headers->get('PHP_AUTH_PW');
+    // Flood protection: this is very similar to the user login form code.
+    // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
+    // Do not allow any login from the current user's IP if the limit has been
+    // reached. Default is 50 failed attempts allowed in one hour. This is
+    // independent of the per-user limit to catch attempts from one IP to log
+    // in to many different user accounts.  We have a reasonably high limit
+    // since there may be only one apparent IP for all users at an institution.
+    if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
+      $accounts = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]);
+      $account = reset($accounts);
+      if ($account) {
+        if ($flood_config->get('uid_only')) {
+          // Register flood events based on the uid only, so they apply for any
+          // IP address. This is the most secure option.
+          $identifier = $account->id();
+        }
+        else {
+          // The default identifier is a combination of uid and IP address. This
+          // is less secure but more resistant to denial-of-service attacks that
+          // could lock out all users with public user names.
+          $identifier = $account->id() . '-' . $request->getClientIP();
+        }
+        // Don't allow login if the limit for this user has been reached.
+        // Default is to allow 5 failed attempts every 6 hours.
+        if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
+          $uid = $this->userAuth->authenticate($username, $password);
+          if ($uid) {
+            $this->flood->clear('basic_auth.failed_login_user', $identifier);
+            return $this->entityManager->getStorage('user')->load($uid);
+          }
+          else {
+            // Register a per-user failed login event.
+            $this->flood->register('basic_auth.failed_login_user', $flood_config->get('user_window'), $identifier);
+          }
+        }
+      }
+    }
+    // Always register an IP-based failed login event.
+    $this->flood->register('basic_auth.failed_login_ip', $flood_config->get('ip_window'));
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function challengeException(Request $request, \Exception $previous) {
+    $site_name = $this->configFactory->get('system.site')->get('name');
+    $challenge = SafeMarkup::format('Basic realm="@realm"', [
+      '@realm' => !empty($site_name) ? $site_name : 'Access restricted',
+    ]);
+    return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
+  }
+
+}