Passed
Pull Request — master (#31)
by Tim
03:43 queued 01:48
created

AuthProcess::main()   C

Complexity

Conditions 14
Paths 50

Size

Total Lines 94
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 55
c 1
b 0
f 0
dl 0
loc 94
rs 6.2666
nc 50
nop 1
cc 14

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 SimpleSAML\Auth;
6
use SimpleSAML\Configuration;
7
use SimpleSAML\Error;
8
use SimpleSAML\HTTP\RunnableResponse;
9
use SimpleSAML\Logger;
10
use SimpleSAML\Module;
11
use SimpleSAML\Module\webauthn\WebAuthn\WebAuthnAbstractEvent;
12
use SimpleSAML\Module\webauthn\WebAuthn\WebAuthnAuthenticationEvent;
13
use SimpleSAML\Session;
14
use SimpleSAML\Utils;
15
use SimpleSAML\XHTML\Template;
16
use Symfony\Component\HttpFoundation\Request;
17
use Symfony\Component\HttpFoundation\RedirectResponse;
18
use Symfony\Component\HttpFoundation\Response;
19
use Symfony\Component\HttpFoundation\StreamedResponse;
20
21
/**
22
 * Controller class for the webauthn module.
23
 *
24
 * This class serves the different views available in the module.
25
 *
26
 * @package SimpleSAML\Module\webauthn
27
 */
28
class AuthProcess
29
{
30
    /** @var \SimpleSAML\Configuration */
31
    protected $config;
32
33
    /** @var \SimpleSAML\Session */
34
    protected $session;
35
36
    /**
37
     * @var \SimpleSAML\Auth\State|string
38
     * @psalm-var \SimpleSAML\Auth\State|class-string
39
     */
40
    protected $authState = Auth\State::class;
41
42
    /**
43
     * @var \SimpleSAML\Logger|string
44
     * @psalm-var \SimpleSAML\Logger|class-string
45
     */
46
    protected $logger = Logger::class;
47
48
49
    /**
50
     * Controller constructor.
51
     *
52
     * It initializes the global configuration and session for the controllers implemented here.
53
     *
54
     * @param \SimpleSAML\Configuration              $config The configuration to use by the controllers.
55
     * @param \SimpleSAML\Session                    $session The session to use by the controllers.
56
     *
57
     * @throws \Exception
58
     */
59
    public function __construct(
60
        Configuration $config,
61
        Session $session
62
    ) {
63
        $this->config = $config;
64
        $this->session = $session;
65
    }
66
67
68
    /**
69
     * Inject the \SimpleSAML\Auth\State dependency.
70
     *
71
     * @param \SimpleSAML\Auth\State $authState
72
     */
73
    public function setAuthState(Auth\State $authState): void
74
    {
75
        $this->authState = $authState;
76
    }
77
78
79
    /**
80
     * Inject the \SimpleSAML\Logger dependency.
81
     *
82
     * @param \SimpleSAML\Logger $logger
83
     */
84
    public function setLogger(Logger $logger): void
85
    {
86
        $this->logger = $logger;
87
    }
88
89
90
    /**
91
     * @param \Symfony\Component\HttpFoundation\Request $request
92
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\SimpleSAML\HTTP\RunnableResponse|\Symfony\Component\HttpFoundation\StreamedResponse
93
     *   A Symfony Response-object.
94
     */
95
    public function main(Request $request): Response
96
    {
97
        if (session_status() != PHP_SESSION_ACTIVE) {
98
            session_cache_limiter('nocache');
99
        }
100
101
        $this->logger::info('FIDO2 - Accessing WebAuthn enrollment validation');
102
103
        $stateId = $request->query->get('StateId');
104
        if ($stateId === null) {
105
            throw new Error\BadRequest('Missing required StateId query parameter.');
106
        }
107
108
        $debugEnabled = $this->config->getValue('logging.level', Logger::NOTICE) === Logger::DEBUG;
109
110
        $state = Auth\State::loadState($stateId, 'webauthn:request');
111
112
        $incomingID = bin2hex(WebAuthnAbstractEvent::base64urlDecode($request->request->get('response_id')));
113
114
        /**
115
         * §7.2 STEP 2 - 4 : check that the credential is one of those the particular user owns
116
         */
117
        $publicKey = false;
118
        $previousCounter = -1;
119
120
        foreach ($state['FIDO2Tokens'] as $oneToken) {
121
            if ($oneToken[0] == $incomingID) {
122
                // Credential ID is eligible for user $state['FIDO2Username'];
123
                // using publicKey $oneToken[1] with current counter value $oneToken[2]
124
                $publicKey = $oneToken[1];
125
                $previousCounter = $oneToken[2];
126
                break;
127
            }
128
        }
129
130
        if ($publicKey === false) {
131
            throw new Exception(
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Module\webauthn\Controller\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
132
                "User attempted to authenticate with an unknown credential ID. This should already have been prevented by the browser!"
133
            );
134
        }
135
136
        /** @psalm-var array $oneToken */
137
        $authObject = new WebAuthnAuthenticationEvent(
138
            $request->request->get('type'),
139
            ($state['FIDO2Scope'] === null ? $state['FIDO2DerivedScope'] : $state['FIDO2Scope']),
140
            $state['FIDO2SignupChallenge'],
141
            $state['IdPMetadata']['entityid'],
142
            base64_decode($request->request->get('authenticator_data')),
143
            base64_decode($request->request->get('client_data_raw')),
144
            $oneToken[0],
1 ignored issue
show
Comprehensibility Best Practice introduced by
The variable $oneToken seems to be defined by a foreach iteration on line 120. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
145
            $oneToken[1],
146
            base64_decode($request->request->get('signature')),
147
            $debugEnabled
148
        );
149
150
        /**
151
         * §7.2 STEP 18 : detect physical object cloning on the token
152
         */
153
        $counter = $authObject->getCounter();
154
        if (($previousCounter != 0 || $counter != 0) && $counter > $previousCounter) {
155
            // Signature counter was incremented compared to last time, good
156
            $store = $state['webauthn:store'];
157
            $store->updateSignCount($oneToken[0], $counter);
158
        } else {
159
            throw new Exception(
160
                "Signature counter less or equal to a previous authentication! Token cloning likely (old: $previousCounter, new: $counter."
161
            );
162
        }
163
164
        // THAT'S IT. The user authenticated successfully. Remember the credential ID that was used.
165
        $state['FIDO2AuthSuccessful'] = $oneToken[0];
166
167
        // See if he wants to hang around for token management operations
168
        if ($request->request->get('credentialChange') === 'on') {
169
            $state['FIDO2WantsRegister'] = true;
170
        } else {
171
            $state['FIDO2WantsRegister'] = false;
172
        }
173
174
        $this->authState::saveState($state, 'webauthn:request');
0 ignored issues
show
Bug introduced by
It seems like $state can also be of type null; however, parameter $state of SimpleSAML\Auth\State::saveState() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        $this->authState::saveState(/** @scrutinizer ignore-type */ $state, 'webauthn:request');
Loading history...
175
176
        if ($debugEnabled) {
177
            $response = new StreamedResponse();
178
            $response->setCallback(function () {
179
                echo $authObject->getDebugBuffer();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $authObject seems to be never defined.
Loading history...
180
                echo $authObject->getValidateBuffer();
181
                echo "Debug mode, not continuing to " . ($state['FIDO2WantsRegister'] ? "credential registration page." : "destination.");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $state seems to be never defined.
Loading history...
182
            });
183
            return $response;
184
        } else {
185
            if ($state['FIDO2WantsRegister']) {
186
                return new RedirectResponse(Module::getModuleURL('webauthn/webauthn.php?StateId=' . urlencode($stateId)));
187
            } else {
188
                return new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]);
189
            }
190
        }
191
    }
192
}
193