SudoModeController::getClientConfig()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 6
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SilverStripe\SecurityExtensions\Control;
6
7
use SilverStripe\Admin\LeftAndMain;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Control\HTTPResponse_Exception;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\ORM\ValidationResult;
13
use SilverStripe\Security\Authenticator;
14
use SilverStripe\Security\Security;
15
use SilverStripe\Security\SecurityToken;
16
use SilverStripe\SecurityExtensions\Service\SudoModeServiceInterface;
17
18
/**
19
 * Responsible for checking and verifying whether sudo mode is enabled
20
 */
21
class SudoModeController extends LeftAndMain
22
{
23
    private static $url_segment = 'sudomode';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
24
25
    private static $ignore_menuitem = true;
0 ignored issues
show
introduced by
The private property $ignore_menuitem is not used, and could be removed.
Loading history...
26
27
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
28
        'check',
29
        'activate',
30
    ];
31
32
    private static $dependencies = [
0 ignored issues
show
introduced by
The private property $dependencies is not used, and could be removed.
Loading history...
33
        'SudoModeService' => '%$' . SudoModeServiceInterface::class,
34
    ];
35
36
    /**
37
     * A user help documentation link to find out more about sudo mode
38
     *
39
     * @config
40
     * @var string
41
     */
42
    // phpcs:ignore 
43
    private static $help_link = 'https://userhelp.silverstripe.org/en/4/optional_features/multi-factor_authentication/user_manual/managing_your_mfa_settings/#managing-your-mfa-settings';
0 ignored issues
show
introduced by
The private property $help_link is not used, and could be removed.
Loading history...
44
45
    /**
46
     * @var SudoModeServiceInterface
47
     */
48
    private $sudoModeService;
49
50
    /**
51
     * Explicitly disable required permissions for sudo mode checks
52
     *
53
     * @var boolean
54
     */
55
    private static $required_permission_codes = false;
0 ignored issues
show
introduced by
The private property $required_permission_codes is not used, and could be removed.
Loading history...
56
57
    public function getClientConfig()
58
    {
59
        /** @var HTTPRequest $request */
60
        $request = Injector::inst()->get(HTTPRequest::class);
61
62
        return array_merge_recursive(parent::getClientConfig(), [
63
            'endpoints' => [
64
                'activate' => $this->Link('activate'),
65
            ],
66
            'sudoModeActive' => $this->getSudoModeService()->check($request->getSession()),
67
            'helpLink' => $this->config()->get('help_link'),
68
        ]);
69
    }
70
71
    /**
72
     * Checks whether sudo mode is active for the current user
73
     *
74
     * @param HTTPRequest $request
75
     * @return HTTPResponse
76
     */
77
    public function check(HTTPRequest $request): HTTPResponse
78
    {
79
        return $this->jsonResponse([
80
            'active' => $this->getSudoModeService()->check($request->getSession()),
81
        ]);
82
    }
83
84
    /**
85
     * After validating the request data including password against the current member, activate sudo mode
86
     * for the current member.
87
     *
88
     * @param HTTPRequest $request
89
     * @return HTTPResponse
90
     * @throws HTTPResponse_Exception If the request was not made with POST
91
     */
92
    public function activate(HTTPRequest $request): HTTPResponse
93
    {
94
        if (!$request->isPOST()) {
95
            return $this->httpError(404);
96
        }
97
98
        if (!SecurityToken::inst()->checkRequest($request)) {
99
            return $this->jsonResponse([
100
                'result' => false,
101
                'message' => _t(__CLASS__ . '.TIMEOUT', 'Session timed out, please refresh and try again.'),
102
            ], 403);
103
        }
104
105
        // Validate password
106
        if (!$this->checkPassword($request)) {
107
            return $this->jsonResponse([
108
                'result' => false,
109
                'message' => _t(__CLASS__ . '.INVALID', 'Incorrect password'),
110
            ]);
111
        }
112
113
        // Activate sudo mode and return successful result
114
        $this->getSudoModeService()->activate($request->getSession());
115
        return $this->jsonResponse(['result' => true]);
116
    }
117
118
    /**
119
     * Checks the provided password is valid for the current member. Will return false if insufficient data
120
     * is available to validate the request.
121
     *
122
     * @param HTTPRequest $request
123
     * @return bool
124
     */
125
    private function checkPassword(HTTPRequest $request): bool
126
    {
127
        $password = $request->postVar('Password');
128
        if (!$password) {
129
            return false;
130
        }
131
132
        $currentMember = Security::getCurrentUser();
133
        if (!$currentMember) {
0 ignored issues
show
introduced by
$currentMember is of type SilverStripe\Security\Member, thus it always evaluated to true.
Loading history...
134
            return false;
135
        }
136
137
        $result = ValidationResult::create();
138
        $authenticators = Security::singleton()->getApplicableAuthenticators(Authenticator::CHECK_PASSWORD);
139
        foreach ($authenticators as $authenticator) {
140
            $authenticator->checkPassword($currentMember, $password, $result);
141
            if (!$result->isValid()) {
142
                break;
143
            }
144
        }
145
        return $result->isValid();
146
    }
147
148
    /**
149
     * Returns a JSON response with an encoded body and provided HTTP status code
150
     *
151
     * @param array $body
152
     * @param int $code
153
     * @return HTTPResponse
154
     */
155
    private function jsonResponse(array $body, int $code = 200): HTTPResponse
156
    {
157
        $response = new HTTPResponse();
158
        $response
159
            ->addHeader('Content-Type', 'application/json')
160
            ->setBody(json_encode($body))
161
            ->setStatusCode($code);
162
        return $response;
163
    }
164
165
    /**
166
     * @param SudoModeServiceInterface $sudoModeService
167
     * @return $this
168
     */
169
    public function setSudoModeService(SudoModeServiceInterface $sudoModeService): self
170
    {
171
        $this->sudoModeService = $sudoModeService;
172
        return $this;
173
    }
174
175
    /**
176
     * @return SudoModeServiceInterface
177
     */
178
    public function getSudoModeService(): ?SudoModeServiceInterface
179
    {
180
        return $this->sudoModeService;
181
    }
182
}
183