git subrepo commit (merge) mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "02ae5285"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "649a5c01"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: I870ad468fba026cc5abf3c5699ed1e12ff28b32b
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php
new file mode 100644
index 0000000..8ba0ef1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Connection.php
@@ -0,0 +1,511 @@
+<?php
+
+namespace LdapRecord;
+
+use Carbon\Carbon;
+use Closure;
+use LdapRecord\Auth\Guard;
+use LdapRecord\Configuration\DomainConfiguration;
+use LdapRecord\Events\DispatcherInterface;
+use LdapRecord\Query\Builder;
+use LdapRecord\Query\Cache;
+use Psr\SimpleCache\CacheInterface;
+
+class Connection
+{
+    use DetectsErrors;
+
+    /**
+     * The underlying LDAP connection.
+     *
+     * @var Ldap
+     */
+    protected $ldap;
+
+    /**
+     * The cache driver.
+     *
+     * @var Cache|null
+     */
+    protected $cache;
+
+    /**
+     * The domain configuration.
+     *
+     * @var DomainConfiguration
+     */
+    protected $configuration;
+
+    /**
+     * The event dispatcher;.
+     *
+     * @var DispatcherInterface|null
+     */
+    protected $dispatcher;
+
+    /**
+     * The current host connected to.
+     *
+     * @var string
+     */
+    protected $host;
+
+    /**
+     * The configured domain hosts.
+     *
+     * @var array
+     */
+    protected $hosts = [];
+
+    /**
+     * The attempted hosts that failed connecting to.
+     *
+     * @var array
+     */
+    protected $attempted = [];
+
+    /**
+     * The callback to execute upon total connection failure.
+     *
+     * @var Closure
+     */
+    protected $failed;
+
+    /**
+     * The authentication guard resolver.
+     *
+     * @var Closure
+     */
+    protected $authGuardResolver;
+
+    /**
+     * Whether the connection is retrying the initial connection attempt.
+     *
+     * @var bool
+     */
+    protected $retryingInitialConnection = false;
+
+    /**
+     * Constructor.
+     *
+     * @param array              $config
+     * @param LdapInterface|null $ldap
+     */
+    public function __construct($config = [], LdapInterface $ldap = null)
+    {
+        $this->setConfiguration($config);
+
+        $this->setLdapConnection($ldap ?? new Ldap());
+
+        $this->failed = function () {
+            $this->dispatch(new Events\ConnectionFailed($this));
+        };
+
+        $this->authGuardResolver = function () {
+            return new Guard($this->ldap, $this->configuration);
+        };
+    }
+
+    /**
+     * Set the connection configuration.
+     *
+     * @param array $config
+     *
+     * @throws Configuration\ConfigurationException
+     *
+     * @return $this
+     */
+    public function setConfiguration($config = [])
+    {
+        $this->configuration = new DomainConfiguration($config);
+
+        $this->hosts = $this->configuration->get('hosts');
+
+        $this->host = reset($this->hosts);
+
+        return $this;
+    }
+
+    /**
+     * Set the LDAP connection.
+     *
+     * @param LdapInterface $ldap
+     *
+     * @return $this
+     */
+    public function setLdapConnection(LdapInterface $ldap)
+    {
+        $this->ldap = $ldap;
+
+        return $this;
+    }
+
+    /**
+     * Set the event dispatcher.
+     *
+     * @param DispatcherInterface $dispatcher
+     *
+     * @return $this
+     */
+    public function setDispatcher(DispatcherInterface $dispatcher)
+    {
+        $this->dispatcher = $dispatcher;
+
+        return $this;
+    }
+
+    /**
+     * Initializes the LDAP connection.
+     *
+     * @return void
+     */
+    public function initialize()
+    {
+        $this->configure();
+
+        $this->ldap->connect($this->host, $this->configuration->get('port'));
+    }
+
+    /**
+     * Configure the LDAP connection.
+     *
+     * @return void
+     */
+    protected function configure()
+    {
+        if ($this->configuration->get('use_ssl')) {
+            $this->ldap->ssl();
+        } elseif ($this->configuration->get('use_tls')) {
+            $this->ldap->tls();
+        }
+
+        $this->ldap->setOptions(array_replace(
+            $this->configuration->get('options'),
+            [
+                LDAP_OPT_PROTOCOL_VERSION => $this->configuration->get('version'),
+                LDAP_OPT_NETWORK_TIMEOUT => $this->configuration->get('timeout'),
+                LDAP_OPT_REFERRALS => $this->configuration->get('follow_referrals'),
+            ]
+        ));
+    }
+
+    /**
+     * Set the cache store.
+     *
+     * @param CacheInterface $store
+     *
+     * @return $this
+     */
+    public function setCache(CacheInterface $store)
+    {
+        $this->cache = new Cache($store);
+
+        return $this;
+    }
+
+    /**
+     * Get the cache store.
+     *
+     * @return Cache|null
+     */
+    public function getCache()
+    {
+        return $this->cache;
+    }
+
+    /**
+     * Get the LDAP configuration instance.
+     *
+     * @return DomainConfiguration
+     */
+    public function getConfiguration()
+    {
+        return $this->configuration;
+    }
+
+    /**
+     * Get the LDAP connection instance.
+     *
+     * @return Ldap
+     */
+    public function getLdapConnection()
+    {
+        return $this->ldap;
+    }
+
+    /**
+     * Bind to the LDAP server.
+     *
+     * If no username or password is specified, then the configured credentials are used.
+     *
+     * @param string|null $username
+     * @param string|null $password
+     *
+     * @throws Auth\BindException
+     * @throws LdapRecordException
+     *
+     * @return Connection
+     */
+    public function connect($username = null, $password = null)
+    {
+        $attempt = function () use ($username, $password) {
+            $this->dispatch(new Events\Connecting($this));
+
+            is_null($username) && is_null($password)
+                ? $this->auth()->bindAsConfiguredUser()
+                : $this->auth()->bind($username, $password);
+
+            $this->dispatch(new Events\Connected($this));
+
+            $this->retryingInitialConnection = false;
+        };
+
+        try {
+            $this->runOperationCallback($attempt);
+        } catch (LdapRecordException $e) {
+            $this->retryingInitialConnection = true;
+
+            $this->retryOnNextHost($e, $attempt);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Reconnect to the LDAP server.
+     *
+     * @throws Auth\BindException
+     * @throws ConnectionException
+     *
+     * @return void
+     */
+    public function reconnect()
+    {
+        $this->reinitialize();
+
+        $this->connect();
+    }
+
+    /**
+     * Reinitialize the connection.
+     *
+     * @return void
+     */
+    protected function reinitialize()
+    {
+        $this->disconnect();
+
+        $this->initialize();
+    }
+
+    /**
+     * Disconnect from the LDAP server.
+     *
+     * @return void
+     */
+    public function disconnect()
+    {
+        $this->ldap->close();
+    }
+
+    /**
+     * Dispatch an event.
+     *
+     * @param object $event
+     *
+     * @return void
+     */
+    public function dispatch($event)
+    {
+        if (isset($this->dispatcher)) {
+            $this->dispatcher->dispatch($event);
+        }
+    }
+
+    /**
+     * Get the attempted hosts that failed connecting to.
+     *
+     * @return array
+     */
+    public function attempted()
+    {
+        return $this->attempted;
+    }
+
+    /**
+     * Perform the operation on the LDAP connection.
+     *
+     * @param Closure $operation
+     *
+     * @return mixed
+     */
+    public function run(Closure $operation)
+    {
+        try {
+            // Before running the operation, we will check if the current
+            // connection is bound and connect if necessary. Otherwise
+            // some LDAP operations will not be executed properly.
+            if (! $this->isConnected()) {
+                $this->connect();
+            }
+
+            return $this->runOperationCallback($operation);
+        } catch (LdapRecordException $e) {
+            if ($exception = $this->getExceptionForCauseOfFailure($e)) {
+                throw $exception;
+            }
+
+            return $this->tryAgainIfCausedByLostConnection($e, $operation);
+        }
+    }
+
+    /**
+     * Attempt to get an exception for the cause of failure.
+     *
+     * @param LdapRecordException $e
+     *
+     * @return mixed
+     */
+    protected function getExceptionForCauseOfFailure(LdapRecordException $e)
+    {
+        switch (true) {
+            case $this->errorContainsMessage($e->getMessage(), 'Already exists'):
+                return Exceptions\AlreadyExistsException::withDetailedError($e, $e->getDetailedError());
+            case $this->errorContainsMessage($e->getMessage(), 'Insufficient access'):
+                return Exceptions\InsufficientAccessException::withDetailedError($e, $e->getDetailedError());
+            case $this->errorContainsMessage($e->getMessage(), 'Constraint violation'):
+                return Exceptions\ConstraintViolationException::withDetailedError($e, $e->getDetailedError());
+            default:
+                return;
+        }
+    }
+
+    /**
+     * Run the operation callback on the current LDAP connection.
+     *
+     * @param Closure $operation
+     *
+     * @throws LdapRecordException
+     *
+     * @return mixed
+     */
+    protected function runOperationCallback(Closure $operation)
+    {
+        return $operation($this->ldap);
+    }
+
+    /**
+     * Get a new auth guard instance.
+     *
+     * @return Auth\Guard
+     */
+    public function auth()
+    {
+        if (! $this->ldap->isConnected()) {
+            $this->initialize();
+        }
+
+        $guard = call_user_func($this->authGuardResolver);
+
+        $guard->setDispatcher(
+            Container::getInstance()->getEventDispatcher()
+        );
+
+        return $guard;
+    }
+
+    /**
+     * Get a new query builder for the connection.
+     *
+     * @return Query\Builder
+     */
+    public function query()
+    {
+        return (new Builder($this))
+            ->setCache($this->cache)
+            ->setBaseDn($this->configuration->get('base_dn'));
+    }
+
+    /**
+     * Determine if the LDAP connection is bound.
+     *
+     * @return bool
+     */
+    public function isConnected()
+    {
+        return $this->ldap->isBound();
+    }
+
+    /**
+     * Attempt to retry an LDAP operation if due to a lost connection.
+     *
+     * @param LdapRecordException $e
+     * @param Closure             $operation
+     *
+     * @throws LdapRecordException
+     *
+     * @return mixed
+     */
+    protected function tryAgainIfCausedByLostConnection(LdapRecordException $e, Closure $operation)
+    {
+        // If the operation failed due to a lost or failed connection,
+        // we'll attempt reconnecting and running the operation again
+        // underneath the same host, and then move onto the next.
+        if ($this->causedByLostConnection($e->getMessage())) {
+            return $this->retry($operation);
+        }
+
+        throw $e;
+    }
+
+    /**
+     * Retry the operation on the current host.
+     *
+     * @param Closure $operation
+     *
+     * @throws LdapRecordException
+     *
+     * @return mixed
+     */
+    protected function retry(Closure $operation)
+    {
+        try {
+            $this->retryingInitialConnection
+                ? $this->reinitialize()
+                : $this->reconnect();
+
+            return $this->runOperationCallback($operation);
+        } catch (LdapRecordException $e) {
+            return $this->retryOnNextHost($e, $operation);
+        }
+    }
+
+    /**
+     * Attempt the operation again on the next host.
+     *
+     * @param LdapRecordException $e
+     * @param Closure             $operation
+     *
+     * @throws LdapRecordException
+     *
+     * @return mixed
+     */
+    protected function retryOnNextHost(LdapRecordException $e, Closure $operation)
+    {
+        $this->attempted[$this->host] = Carbon::now();
+
+        if (($key = array_search($this->host, $this->hosts)) !== false) {
+            unset($this->hosts[$key]);
+        }
+
+        if ($next = reset($this->hosts)) {
+            $this->host = $next;
+
+            return $this->tryAgainIfCausedByLostConnection($e, $operation);
+        }
+
+        call_user_func($this->failed, $this->ldap);
+
+        throw $e;
+    }
+}