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