Passed
Pull Request — master (#28)
by
unknown
02:15
created

WebAuthn::__construct()   F

Complexity

Conditions 12
Paths 528

Size

Total Lines 70
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 12
eloc 44
c 6
b 1
f 0
nc 528
nop 2
dl 0
loc 70
rs 3.4555

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
/**
4
 * FIDO2/WebAuthn Authentication Processing filter
5
 *
6
 * Filter for registering or authenticating with a FIDO2/WebAuthn token after
7
 * having authenticated with the primary authsource.
8
 *
9
 * @author Stefan Winter <[email protected]>
10
 * @package SimpleSAMLphp
11
 */
12
13
namespace SimpleSAML\Module\webauthn\Auth\Process;
14
15
use SimpleSAML\Auth;
16
use SimpleSAML\Error;
17
use SimpleSAML\Logger;
18
use SimpleSAML\Module;
19
use SimpleSAML\Module\webauthn\Store;
20
use SimpleSAML\Utils;
21
22
class WebAuthn extends Auth\ProcessingFilter
23
{
24
    /**
25
     * backend storage configuration. Required.
26
     *
27
     * @var \SimpleSAML\Module\webauthn\Store
28
     */
29
    private $store;
30
31
    /**
32
     * Scope of the FIDO2 attestation. Can only be in the own domain.
33
     *
34
     * @var string|null
35
     */
36
    private $scope = null;
37
38
    /**
39
     * The scope derived from the SimpleSAMLphp configuration;
40
     * can be null due to misconfiguration, in case we cannot warn the administrator on a mismatching scope
41
     *
42
     * @var string|null
43
     */
44
    private $derivedScope = null;
45
46
    /**
47
     * attribute to use as username for the FIDO2 attestation.
48
     *
49
     * @var string
50
     */
51
    private $usernameAttrib;
52
53
    /**
54
     * attribute to use as display name for the FIDO2 attestation.
55
     *
56
     * @var string
57
     */
58
    private $displaynameAttrib;
59
60
    /**
61
     * @var boolean
62
     */
63
    private $requestTokenModel;
64
65
    /**
66
     * @var boolean should new users be considered as enabled by default?
67
     */
68
    private $defaultEnabled;
69
70
    /**
71
     * @var boolean switch that determines how $toggle will be used, if true then value of $toggle
72
     *              will mean whether to trigger (true) or not (false) the webauthn authentication,
73
     *              if false then $toggle means whether to switch the value of $defaultEnabled and then use that
74
     */
75
    private $force;
76
77
    /**
78
     * @var boolean an attribute which is associated with $force because it determines its meaning,
79
     *              it either simply means whether to trigger webauthn authentication or switch the default settings,
80
     *              if null (was not sent as attribute) then the information from database is used
81
     */
82
    private $toggleAttrib;
83
84
    /**
85
     * @var bool a bool that determines whether to use local database or not
86
     */
87
    private $useDatabase;
88
89
    /**
90
     * @var string|null AuthnContextClassRef
91
     */
92
    private $authnContextClassRef = null;
93
94
    /**
95
     * Initialize filter.
96
     *
97
     * Validates and parses the configuration.
98
     *
99
     * @param array $config Configuration information.
100
     * @param mixed $reserved For future use.
101
     *
102
     * @throws \SimpleSAML\Error\Exception if the configuration is not valid.
103
     */
104
    public function __construct($config, $reserved)
105
    {
106
        /**
107
         * Remove annotation + assert as soon as this method can be typehinted (SSP 2.0)
108
         * @psalm-suppress RedundantConditionGivenDocblockType
109
         */
110
        assert(is_array($config));
111
        parent::__construct($config, $reserved);
112
113
        try {
114
            $this->store = Store::parseStoreConfig($config['store']);
115
        } catch (\Exception $e) {
116
            Logger::error(
117
                'webauthn: Could not create storage: ' .
118
                $e->getMessage()
119
            );
120
        }
121
122
        // Set the optional scope if set by configuration
123
        if (array_key_exists('scope', $config)) {
124
            $this->scope = $config['scope'];
125
        }
126
127
        // Set the derived scope so we can compare it to the sent host at a later point
128
        $baseurl = Utils\HTTP::getSelfHost();
129
        $hostname = parse_url($baseurl, PHP_URL_HOST);
130
        if ($hostname !== null) {
131
            $this->derivedScope = $hostname;
132
        }
133
134
        if (array_key_exists('attrib_username', $config)) {
135
            $this->usernameAttrib = $config['attrib_username'];
136
        } else {
137
            throw new Error\CriticalConfigurationError('webauthn: it is required to set attrib_username in config.');
138
        }
139
140
        if (array_key_exists('attrib_displayname', $config)) {
141
            $this->displaynameAttrib = $config['attrib_displayname'];
142
        } else {
143
            throw new Error\CriticalConfigurationError('webauthn: it is required to set attrib_displayname in config.');
144
        }
145
146
        if (array_key_exists('request_tokenmodel', $config)) {
147
            $this->requestTokenModel = $config['request_tokenmodel'];
148
        } else {
149
            $this->requestTokenModel = false;
150
        }
151
        if (array_key_exists('default_enable', $config)) {
152
            $this->defaultEnabled = $config['default_enable'];
153
        } else {
154
            $this->defaultEnabled = false;
155
        }
156
157
        if (array_key_exists('force', $config)) {
158
            $this->force = $config['force'];
159
        } else {
160
            $this->force = true;
161
        }
162
        if (array_key_exists('attrib_toggle', $config)) {
163
            $this->toggleAttrib = $config['attrib_toggle'];
164
        } else {
165
            $this->toggleAttrib = 'toggle';
0 ignored issues
show
Documentation Bug introduced by
The property $toggleAttrib was declared of type boolean, but 'toggle' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
166
        }
167
        if (array_key_exists('use_database', $config)) {
168
            $this->useDatabase = $config['use_database'];
169
        } else {
170
            $this->useDatabase = true;
171
        }
172
        if (array_key_exists('authnContextClassRef', $config)) {
173
            $this->authnContextClassRef = $config['authnContextClassRef'];
174
175
        }
176
    }
177
178
    /**
179
     * Process a authentication response
180
     *
181
     * This function saves the state, and redirects the user to the page where
182
     * the user can register or authenticate with his token.
183
     *
184
     * @param array &$state The state of the response.
185
     *
186
     * @return void
187
     */
188
    public function process(&$state)
189
    {
190
        /**
191
         * Remove annotation + assert as soon as this method can be typehinted (SSP 2.0)
192
         * @psalm-suppress RedundantConditionGivenDocblockType
193
         */
194
        assert(is_array($state));
195
        assert(array_key_exists('UserID', $state));
196
        assert(array_key_exists('Destination', $state));
197
        assert(array_key_exists('entityid', $state['Destination']));
198
        assert(array_key_exists('metadata-set', $state['Destination']));
199
        assert(array_key_exists('entityid', $state['Source']));
200
        assert(array_key_exists('metadata-set', $state['Source']));
201
202
        if (!array_key_exists($this->usernameAttrib, $state['Attributes'])) {
203
            Logger::warning('webauthn: cannot determine if user needs second factor, missing attribute "' .
204
                $this->usernameAttrib . '".');
205
            return;
206
        }
207
208
        $state['requestTokenModel'] = $this->requestTokenModel;
209
        $state['webauthn:store'] = $this->store;
210
        $state['saml:AuthnContextClassRef'] = $this->authnContextClassRef ?? 'urn:rsa:names:tc:SAML:2.0:ac:classes:FIDO';
211
212
        Logger::debug('webauthn: userid: ' . $state['Attributes'][$this->usernameAttrib][0]);
213
214
        $localToggle = !empty($state['Attributes'][$this->toggleAttrib])
215
            && !empty($state['Attributes'][$this->toggleAttrib][0]);
216
217
        if (
218
            $this->store->is2FAEnabled(
219
                $state['Attributes'][$this->usernameAttrib][0],
220
                $this->defaultEnabled,
221
                $this->useDatabase,
222
                $localToggle,
223
                $this->force
224
            ) === false
225
        ) {
226
            // nothing to be done here, end authprocfilter processing
227
            return;
228
        }
229
230
        $state['FIDO2Tokens'] = $this->store->getTokenData($state['Attributes'][$this->usernameAttrib][0]);
231
        $state['FIDO2Scope'] = $this->scope;
232
        $state['FIDO2DerivedScope'] = $this->derivedScope;
233
        $state['FIDO2Username'] = $state['Attributes'][$this->usernameAttrib][0];
234
        $state['FIDO2Displayname'] = $state['Attributes'][$this->displaynameAttrib][0];
235
        $state['FIDO2SignupChallenge'] = hash('sha512', random_bytes(64));
236
        $state['FIDO2WantsRegister'] = false;
237
        $state['FIDO2AuthSuccessful'] = false;
238
239
        // Save state and redirect
240
        $id = Auth\State::saveState($state, 'webauthn:request');
241
        $url = Module::getModuleURL('webauthn/webauthn.php');
242
        Utils\HTTP::redirectTrustedURL($url, ['StateId' => $id]);
243
    }
244
}
245