Issues (186)

includes/Tasks/InternalPageBase.php (1 issue)

Severity
1
<?php
2
/******************************************************************************
3
 * Wikipedia Account Creation Assistance tool                                 *
4
 * ACC Development Team. Please see team.json for a list of contributors.     *
5
 *                                                                            *
6
 * This is free and unencumbered software released into the public domain.    *
7
 * Please see LICENSE.md for the full licencing statement.                    *
8
 ******************************************************************************/
9
10
namespace Waca\Tasks;
11
12
use Exception;
13
use PDO;
14
use Waca\DataObjects\SiteNotice;
15
use Waca\DataObjects\User;
16
use Waca\Exceptions\AccessDeniedException;
17
use Waca\Exceptions\NotIdentifiedException;
18
use Waca\Fragments\NavigationMenuAccessControl;
19
use Waca\Helpers\Interfaces\IBlacklistHelper;
20
use Waca\Helpers\Interfaces\ITypeAheadHelper;
21
use Waca\Security\IDomainAccessManager;
22
use Waca\Security\ISecurityManager;
23
use Waca\WebRequest;
24
25
abstract class InternalPageBase extends PageBase
26
{
27
    use NavigationMenuAccessControl;
28
29
    /** @var ITypeAheadHelper */
30
    private $typeAheadHelper;
31
    private ISecurityManager $securityManager;
32
    /** @var IBlacklistHelper */
33
    private $blacklistHelper;
34
35
    private IDomainAccessManager $domainAccessManager;
36
37
    /**
38
     * @return ITypeAheadHelper
39
     */
40
    public function getTypeAheadHelper()
41
    {
42
        return $this->typeAheadHelper;
43
    }
44
45
    /**
46
     * @param ITypeAheadHelper $typeAheadHelper
47
     */
48
    public function setTypeAheadHelper(ITypeAheadHelper $typeAheadHelper)
49
    {
50
        $this->typeAheadHelper = $typeAheadHelper;
51
    }
52
53
    /**
54
     * Runs the page code
55
     *
56
     * @throws Exception
57
     * @category Security-Critical
58
     */
59
    final public function execute()
60
    {
61
        if ($this->getRouteName() === null) {
0 ignored issues
show
The condition $this->getRouteName() === null is always false.
Loading history...
62
            throw new Exception("Request is unrouted.");
63
        }
64
65
        if ($this->getSiteConfiguration() === null) {
66
            throw new Exception("Page has no configuration!");
67
        }
68
69
        $this->setupPage();
70
71
        $this->touchUserLastActive();
72
73
        $currentUser = User::getCurrent($this->getDatabase());
74
75
        // Hey, this is also a security barrier, in addition to the below. Separated out for readability.
76
        if (!$this->isProtectedPage()) {
77
            // This page is /not/ a protected page, as such we can just run it.
78
            $this->runPage();
79
80
            return;
81
        }
82
83
        // Security barrier.
84
        //
85
        // This code essentially doesn't care if the user is logged in or not, as the security manager hides all that
86
        // away for us
87
        $securityResult = $this->getSecurityManager()->allows(get_called_class(), $this->getRouteName(), $currentUser);
88
        if ($securityResult === ISecurityManager::ALLOWED) {
89
            // We're allowed to run the page, so let's run it.
90
            $this->runPage();
91
        }
92
        else {
93
            $this->handleAccessDenied($securityResult);
94
95
            // Send the headers
96
            $this->sendResponseHeaders();
97
        }
98
    }
99
100
    /**
101
     * Performs final tasks needed before rendering the page.
102
     */
103
    final public function finalisePage()
104
    {
105
        parent::finalisePage();
106
107
        $database = $this->getDatabase();
108
        $currentUser = User::getCurrent($database);
109
110
        // Load in the badges for the navbar
111
        $this->setUpNavBarBadges($currentUser, $database);
112
113
        if ($this->barrierTest('viewSiteNotice', User::getCurrent($database), 'GlobalInfo')) {
114
            $siteNotice = SiteNotice::get($this->getDatabase());
115
            $siteNoticeHash = sha1($siteNotice);
116
117
            if (WebRequest::testSiteNoticeCookieValue($siteNoticeHash)) {
118
                $this->assign('siteNoticeState', 'd-none');
119
            }
120
            else {
121
                $this->assign('siteNoticeState', 'd-block');
122
            }
123
124
            $this->assign('siteNoticeText', $siteNotice);
125
            $this->assign('siteNoticeVersion', $siteNoticeHash);
126
        }
127
128
        if ($this->barrierTest('viewOnlineUsers', User::getCurrent($database), 'GlobalInfo')) {
129
            $sql = 'SELECT * FROM user WHERE lastactive > DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 5 MINUTE);';
130
            $statement = $database->query($sql);
131
            $activeUsers = $statement->fetchAll(PDO::FETCH_CLASS, User::class);
132
            $this->assign('onlineusers', $activeUsers);
133
        }
134
135
        $this->setupNavMenuAccess($currentUser);
136
    }
137
138
    /**
139
     * Configures whether the page respects roles or not. You probably want this to return true.
140
     *
141
     * Set to false for public pages. You probably want this to return true.
142
     *
143
     * This defaults to true unless you explicitly set it to false. Setting it to false means anybody can do anything
144
     * on this page, so you probably want this to return true.
145
     *
146
     * @return bool
147
     * @category Security-Critical
148
     */
149
    protected function isProtectedPage()
150
    {
151
        return true;
152
    }
153
154
    protected function handleAccessDenied($denyReason)
155
    {
156
        $currentUser = User::getCurrent($this->getDatabase());
157
158
        // Not allowed to access this resource.
159
        // Firstly, let's check if we're even logged in.
160
        if ($currentUser->isCommunityUser()) {
161
            // Not logged in, redirect to login page
162
            WebRequest::setPostLoginRedirect();
163
            $this->redirect("login");
164
165
            return;
166
        }
167
        else {
168
            // Decide whether this was a rights failure, or an identification failure.
169
170
            if ($denyReason === ISecurityManager::ERROR_NOT_IDENTIFIED) {
171
                // Not identified
172
                throw new NotIdentifiedException($this->getSecurityManager(), $this->getDomainAccessManager());
173
            }
174
            elseif ($denyReason === ISecurityManager::ERROR_DENIED) {
175
                // Nope, plain old access denied
176
                throw new AccessDeniedException($this->getSecurityManager(), $this->getDomainAccessManager());
177
            }
178
            else {
179
                throw new Exception('Unknown response from security manager.');
180
            }
181
        }
182
    }
183
184
    /**
185
     * Tests the security barrier for a specified action.
186
     *
187
     * Don't use within templates
188
     *
189
     * @param string      $action
190
     *
191
     * @param User        $user
192
     * @param null|string $pageName
193
     *
194
     * @return bool
195
     * @category Security-Critical
196
     */
197
    final public function barrierTest($action, User $user, $pageName = null)
198
    {
199
        $page = get_called_class();
200
        if ($pageName !== null) {
201
            $page = $pageName;
202
        }
203
204
        $securityResult = $this->getSecurityManager()->allows($page, $action, $user);
205
206
        return $securityResult === ISecurityManager::ALLOWED;
207
    }
208
209
    /**
210
     * Updates the lastactive timestamp
211
     */
212
    private function touchUserLastActive()
213
    {
214
        if (WebRequest::getSessionUserId() !== null) {
215
            $query = 'UPDATE user SET lastactive = CURRENT_TIMESTAMP() WHERE id = :id;';
216
            $this->getDatabase()->prepare($query)->execute(array(":id" => WebRequest::getSessionUserId()));
217
        }
218
    }
219
220
    public function getSecurityManager(): ISecurityManager
221
    {
222
        return $this->securityManager;
223
    }
224
225
    public function setSecurityManager(ISecurityManager $securityManager)
226
    {
227
        $this->securityManager = $securityManager;
228
    }
229
230
    /**
231
     * @return IBlacklistHelper
232
     */
233
    public function getBlacklistHelper()
234
    {
235
        return $this->blacklistHelper;
236
    }
237
238
    /**
239
     * @param IBlacklistHelper $blacklistHelper
240
     */
241
    public function setBlacklistHelper(IBlacklistHelper $blacklistHelper)
242
    {
243
        $this->blacklistHelper = $blacklistHelper;
244
    }
245
246
    public function getDomainAccessManager(): IDomainAccessManager
247
    {
248
        return $this->domainAccessManager;
249
    }
250
251
    public function setDomainAccessManager(IDomainAccessManager $domainAccessManager): void
252
    {
253
        $this->domainAccessManager = $domainAccessManager;
254
    }
255
}
256