Passed
Pull Request — master (#31)
by Tim
02:17
created

RegProcess::main()   C

Complexity

Conditions 12
Paths 48

Size

Total Lines 102
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 61
c 2
b 1
f 0
dl 0
loc 102
rs 6.4242
cc 12
nc 48
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SimpleSAML\Module\webauthn\Controller;
4
5
use Exception;
6
use SimpleSAML\Auth;
7
use SimpleSAML\Configuration;
8
use SimpleSAML\Error;
9
use SimpleSAML\HTTP\RunnableResponse;
10
use SimpleSAML\Locale\Translate;
11
use SimpleSAML\Logger;
12
use SimpleSAML\Module;
13
use SimpleSAML\Module\webauthn\WebAuthn\AAGUID;
14
use SimpleSAML\Module\webauthn\WebAuthn\WebAuthnRegistrationEvent;
15
use SimpleSAML\Session;
16
use SimpleSAML\Utils;
17
use SimpleSAML\XHTML\Template;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\RedirectResponse;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\HttpFoundation\StreamedResponse;
22
23
/**
24
 * Controller class for the webauthn module.
25
 *
26
 * This class serves the different views available in the module.
27
 *
28
 * @package SimpleSAML\Module\webauthn
29
 */
30
class RegProcess
31
{
32
    /** @var \SimpleSAML\Configuration */
33
    protected $config;
34
35
    /** @var \SimpleSAML\Session */
36
    protected $session;
37
38
    /**
39
     * @var \SimpleSAML\Auth\State|string
40
     * @psalm-var \SimpleSAML\Auth\State|class-string
41
     */
42
    protected $authState = Auth\State::class;
43
44
    /**
45
     * @var \SimpleSAML\Logger|string
46
     * @psalm-var \SimpleSAML\Logger|class-string
47
     */
48
    protected $logger = Logger::class;
49
50
51
    /**
52
     * Controller constructor.
53
     *
54
     * It initializes the global configuration and session for the controllers implemented here.
55
     *
56
     * @param \SimpleSAML\Configuration              $config The configuration to use by the controllers.
57
     * @param \SimpleSAML\Session                    $session The session to use by the controllers.
58
     *
59
     * @throws \Exception
60
     */
61
    public function __construct(
62
        Configuration $config,
63
        Session $session
64
    ) {
65
        $this->config = $config;
66
        $this->session = $session;
67
    }
68
69
70
    /**
71
     * Inject the \SimpleSAML\Auth\State dependency.
72
     *
73
     * @param \SimpleSAML\Auth\State $authState
74
     */
75
    public function setAuthState(Auth\State $authState): void
76
    {
77
        $this->authState = $authState;
78
    }
79
80
81
    /**
82
     * Inject the \SimpleSAML\Logger dependency.
83
     *
84
     * @param \SimpleSAML\Logger $logger
85
     */
86
    public function setLogger(Logger $logger): void
87
    {
88
        $this->logger = $logger;
89
    }
90
91
92
    /**
93
     * @param \Symfony\Component\HttpFoundation\Request $request
94
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\SimpleSAML\HTTP\RunnableResponse|\Symfony\Component\HttpFoundation\StreamedResponse
95
     *   A Symfony Response-object.
96
     */
97
    public function main(Request $request): Response
98
    {
99
        if (session_status() != PHP_SESSION_ACTIVE) {
100
            session_cache_limiter('nocache');
101
        }
102
103
        $this->logger::info('FIDO2 - Accessing WebAuthn enrollment validation');
104
105
        $stateId = $request->query->get('StateId');
106
        if ($stateId === null) {
107
            throw new Error\BadRequest('Missing required StateId query parameter.');
108
        }
109
110
        $debugEnabled = $this->config->getValue('logging.level', Logger::NOTICE) === Logger::DEBUG;
111
112
        /** @var array $state */
113
        $state = $this->authState::loadState($stateId, 'webauthn:request');
114
115
        // registering a credential is only allowed for new users or after being authenticated
116
        if (count($state['FIDO2Tokens']) > 0 && $state['FIDO2AuthSuccessful'] === false) {
117
            throw new Exception("Attempt to register new token in unacceptable context.");
118
        }
119
120
        $fido2Scope = ($state['FIDO2Scope'] === null ? $state['FIDO2DerivedScope'] : $state['FIDO2Scope']);
121
        if ($fido2Scope === null) {
122
            throw new Exception("FIDO2Scope cannot be null!");
123
        }
124
125
        $regObject = new WebAuthnRegistrationEvent(
126
            $request->request->get('type'),
127
            $fido2Scope,
128
            $state['FIDO2SignupChallenge'],
129
            $state['IdPMetadata']['entityid'],
130
            base64_decode($_POST['attestation_object']),
131
            $request->request->get('response_id'),
132
            $request->request->get('attestation_client_data_json'),
133
            $debugEnabled
134
        );
135
136
        // at this point, we need to talk to the DB
137
        /**
138
         * STEP 19 of the validation procedure in § 7.1 of the spec: see if this credential is already registered
139
         */
140
        $store = $state['webauthn:store'];
141
        if ($store->doesCredentialExist(bin2hex($regObject->getCredentialId())) === false) {
142
            // credential does not exist yet in database, good.
143
        } else {
144
            throw new Exception("The credential with ID " . $regObject->getCredentialId() . " already exists.");
145
        }
146
147
        // THAT'S IT. This is a valid credential and can be enrolled to the user.
148
        $friendlyName = $request->request->get('tokenname');
149
150
        // if we have requested the token model, add it to the name
151
        if ($state['requestTokenModel']) {
152
            $model = Translate::noop('unknown model');
153
            $vendor = Translate::noop('unknown vendor');
154
            $aaguiddict = AAGUID::getInstance();
155
            if ($aaguiddict->hasToken($regObject->getAAGUID())) {
156
                $token = $aaguiddict->get($regObject->getAAGUID());
157
                $model = $token['model'];
158
                $vendor = $token['O'];
159
            }
160
            $friendlyName .= " ($model [$vendor])";
161
        }
162
163
        /**
164
         * STEP 20 of the validation procedure in § 7.1 of the spec: store credentialId, credential,
165
         * signCount and associate with user
166
         */
167
168
        $store->storeTokenData(
169
            $state['FIDO2Username'],
170
            $regObject->getCredentialId(),
171
            $regObject->getCredential(),
172
            $regObject->getCounter(),
173
            $friendlyName
174
        );
175
176
        // make sure $state gets the news, the token is to be displayed to the user on the next page
177
        $state['FIDO2Tokens'][] = [
178
            0 => $regObject->getCredentialId(),
179
            1 => $regObject->getCredential(),
180
            2 => $regObject->getCounter(),
181
            3 => $friendlyName
182
        ];
183
184
        $id = $this->authState::saveState($state, 'webauthn:request');
185
        if ($debugEnabled === true) {
186
            $response = new StreamedResponse();
187
            $response->setCallback(function ($regObject, $id) {
188
                echo $regObject->getDebugBuffer();
189
                echo $regObject->getValidateBuffer();
190
                echo "<form id='regform' method='POST' action='" .
191
                    Module::getModuleURL('webauthn/webauthn.php?StateId=' . urlencode($id)) . "'>";
192
                echo "<button type='submit'>Return to previous page.</button>";
193
            });
194
            return $response;
195
        } elseif (array_key_exists('Registration', $state)) {
196
            return new RedirectResponse(Module::getModuleURL('webauthn/webauthn.php?StateId=' . urlencode($id)));
197
        } else {
198
            return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
199
        }
200
    }
201
}
202