Completed
Push — master ( bab30e...5d9c47 )
by Stefan
18s queued 14s
created

RegProcess::main()   C

Complexity

Conditions 11
Paths 24

Size

Total Lines 121
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

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