1<?php
2/**
3 * This file is part of the FreeDSx LDAP package.
4 *
5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace FreeDSx\Ldap\Protocol;
12
13use FreeDSx\Ldap\Operation\Request\AnonBindRequest;
14use FreeDSx\Ldap\Operation\Request\BindRequest;
15use FreeDSx\Ldap\Operation\Request\ExtendedRequest;
16use FreeDSx\Ldap\Operation\Request\RequestInterface;
17use FreeDSx\Ldap\Operation\Request\SearchRequest;
18use FreeDSx\Ldap\Operation\Request\SimpleBindRequest;
19use FreeDSx\Ldap\Operation\Request\UnbindRequest;
20use FreeDSx\Ldap\Server\Token\AnonToken;
21use FreeDSx\Ldap\Server\Token\BindToken;
22use FreeDSx\Ldap\Server\Token\TokenInterface;
23
24/**
25 * Abstracts out some of the server authorization logic.
26 *
27 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
28 */
29class ServerAuthorization
30{
31    /**
32     * @var bool
33     */
34    protected $isAuthRequired;
35
36    /**
37     * @var bool
38     */
39    protected $isAnonymousAllowed;
40
41    /**
42     * @var TokenInterface
43     */
44    protected $token;
45
46    public function __construct(TokenInterface $token = null, array $options = [])
47    {
48        $this->token = $token ?? new AnonToken();
49        $this->isAuthRequired = isset($options['require_authentication']) ? (bool) $options['require_authentication'] : true;
50        $this->isAnonymousAllowed = isset($options['allow_anonymous']) ? (bool) $options['allow_anonymous'] : false;
51    }
52
53    /**
54     * Helps determine if a specific request type actually requires authentication to complete.
55     *
56     * @param RequestInterface $request
57     * @return bool
58     */
59    public function isAuthenticationRequired(RequestInterface $request): bool
60    {
61        if ($this->isAuthRequired === false) {
62            return  false;
63        }
64
65        if ($request instanceof ExtendedRequest && $request->getName() === ExtendedRequest::OID_WHOAMI) {
66            return false;
67        } elseif ($request instanceof ExtendedRequest && $request->getName() === ExtendedRequest::OID_START_TLS) {
68            return false;
69        } elseif ($request instanceof UnbindRequest) {
70            return false;
71        } elseif ($request instanceof BindRequest) {
72            return false;
73        } elseif ($this->isRootDseSearch($request)) {
74            return false;
75        }
76
77        return true;
78    }
79
80    /**
81     * Determine if the bind type is actually supported. Anonymous binding may be disabled.
82     */
83    public function isAuthenticationTypeSupported(RequestInterface $request): bool
84    {
85        if ($request instanceof AnonBindRequest) {
86            return $this->isAnonymousAllowed;
87        }
88
89        return $request instanceof SimpleBindRequest;
90    }
91
92    /**
93     * Determine if the incoming request is an authentication attempt.
94     */
95    public function isAuthenticationRequest(RequestInterface $request): bool
96    {
97        return $request instanceof BindRequest;
98    }
99
100    /**
101     * Determine if the current token is "authenticated". In the case where authentication is not required, we always
102     * return true.
103     */
104    public function isAuthenticated(): bool
105    {
106        if ($this->isAuthRequired === false) {
107            return true;
108        }
109
110        return $this->token instanceof BindToken;
111    }
112
113    /**
114     * Set the current token.
115     */
116    public function setToken(TokenInterface $token): void
117    {
118        $this->token = $token;
119    }
120
121    /**
122     * Get the current token.
123     */
124    public function getToken(): TokenInterface
125    {
126        return $this->token;
127    }
128
129    /**
130     * @param RequestInterface $request
131     * @return bool
132     */
133    protected function isRootDseSearch(RequestInterface $request): bool
134    {
135        if (!$request instanceof SearchRequest) {
136            return false;
137        }
138
139        return $request->getScope() === SearchRequest::SCOPE_BASE_OBJECT
140            && ((string) $request->getBaseDn() === '');
141    }
142}
143